From b2316b0029c52840e52c5c80dadb5d3a01944cf1 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 22:13:31 +0200 Subject: [PATCH 01/21] split up app/lib --- app/build.gradle.kts | 148 ++++++++++++++++++ app/detekt-baseline.xml | 8 + app/pom.xml | 57 +++++++ .../net/thauvin/erik/urlencoder/UrlEncoder.kt | 106 +++++++++++++ .../thauvin/erik/urlencoder/UrlEncoderTest.kt | 66 -------- lib/build.gradle.kts | 55 ++++--- lib/detekt-baseline.xml | 17 +- lib/pom.xml | 6 +- .../{UrlEncoder.kt => UrlEncoderUtil.kt} | 49 +----- .../erik/urlencoder/UrlEncoderUtilTest.kt | 114 ++++++++++++++ settings.gradle.kts | 3 + 11 files changed, 477 insertions(+), 152 deletions(-) create mode 100644 app/build.gradle.kts create mode 100644 app/detekt-baseline.xml create mode 100644 app/pom.xml create mode 100644 app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt rename {lib => app}/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt (64%) rename lib/src/main/kotlin/net/thauvin/erik/urlencoder/{UrlEncoder.kt => UrlEncoderUtil.kt} (80%) create mode 100644 lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..9b2a918 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,148 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * 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. + */ + +import buildsrc.utils.Rife2TestListener +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + +plugins { + id("application") + id("com.github.ben-manes.versions") + id("io.gitlab.arturbosch.detekt") + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlinx.kover") + + buildsrc.conventions.publishing + buildsrc.conventions.sonarqube +} + +val mavenName = "UrlEncoder" +val deployDir = project.layout.projectDirectory.dir("deploy") +val gitHub = "ethauvin/${rootProject.name}" +val mavenUrl = "https://github.com/$gitHub" +val publicationName = "mavenJava" +val myClassName = "$group.${rootProject.name}.$mavenName" + +dependencies { + implementation(projects.lib) +// testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") +} + +base { + archivesName.set(rootProject.name) +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + withSourcesJar() +} + +application { + mainClass.set(myClassName) +} + + +tasks { + jar { + manifest { + attributes["Main-Class"] = myClassName + } + } + + val fatJar = register("fatJar") { + group = "build" + dependsOn.addAll(listOf("compileJava", "compileKotlin", "processResources")) + archiveClassifier.set("all") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { attributes(mapOf("Main-Class" to application.mainClass)) } + val sourcesMain = sourceSets.main.get() + val contents = configurations.runtimeClasspath.get() + .map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output + from(contents) + } + + build { + dependsOn(fatJar) + } + + withType().configureEach { + kotlinOptions.jvmTarget = java.targetCompatibility.toString() + } + + test { + addTestListener(Rife2TestListener(project.properties["testsBadgeApiKey"]?.toString())) + } + + withType().configureEach { + useJUnitPlatform() + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + } + + withType().configureEach { + destination = file("$projectDir/pom.xml") + } + + clean { + delete(deployDir) + } + + withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("UrlEncoder Application") + } + } + } + + val copyToDeploy by registering(Sync::class) { + from(configurations.runtimeClasspath) { + exclude("annotations-*.jar") + } + from(jar) + into(deployDir) + } + + register("deploy") { + description = "Copies all needed files to the 'deploy' directory." + group = PublishingPlugin.PUBLISH_TASK_GROUP + dependsOn(build, jar) + outputs.dir(deployDir) + inputs.files(copyToDeploy) + } + + "sonar" { + dependsOn(koverReport) + } +} + +publishing { + publications { + create("mavenJava") { + from(components["java"]) + artifactId = rootProject.name + artifact(tasks.javadocJar) + } + } +} diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml new file mode 100644 index 0000000..fd4e62e --- /dev/null +++ b/app/detekt-baseline.xml @@ -0,0 +1,8 @@ + + + + + ComplexCondition:UrlEncoder.kt$UrlEncoder$hasOption && args.size == 2 || !hasOption && args.size == 1 + MaxLineLength:UrlEncoder.kt$UrlEncoder$* + + diff --git a/app/pom.xml b/app/pom.xml new file mode 100644 index 0000000..57aff04 --- /dev/null +++ b/app/pom.xml @@ -0,0 +1,57 @@ + + + + + + + + 4.0.0 + urlencoder + urlencoder + unspecified + UrlEncoder for Kotlin + https://github.com/ethauvin/urlencoder + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + gbevin + Geert Bevin + gbevin@uwyn.com + https://github.com/gbevin + + + ethauvin + Erik C. Thauvin + erik@thauvin.net + https://erik.thauvin.net/ + + + + scm:git://github.com/ethauvin/urlencoder.git + scm:git@github.com:ethauvin/urlencoder.git + https://github.com/ethauvin/urlencoder + + + GitHub + https://github.com/ethauvin/urlencoder/issues + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.8.0 + compile + + + urlencoder + urlencoder-lib + unspecified + runtime + + + diff --git a/app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt b/app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt new file mode 100644 index 0000000..902bc45 --- /dev/null +++ b/app/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * 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 net.thauvin.erik.urlencoder + +import kotlin.system.exitProcess + +/** + * Most defensive approach to URL encoding and decoding. + * + * - Rules determined by combining the unreserved character set from + * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#page-13) with the percent-encode set from + * [application/x-www-form-urlencoded](https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set). + * + * - Both specs above support percent decoding of two hexadecimal digits to a binary octet, however their unreserved + * set of characters differs and `application/x-www-form-urlencoded` adds conversion of space to `+`, which has the + * potential to be misunderstood. + * + * - This library encodes with rules that will be decoded correctly in either case. + * + * @author Geert Bevin (gbevin(remove) at uwyn dot com) + * @author Erik C. Thauvin (erik@thauvin.net) + **/ +object UrlEncoder { + + internal val usage = + "Usage : java -jar urlencoder-*all.jar [-ed] text" + System.lineSeparator() + + "Encode and decode URL components defensively." + System.lineSeparator() + + " -e encode (default) " + System.lineSeparator() + + " -d decode" + + /** + * Encodes and decodes URLs from the command line. + * + * - `java -jar urlencoder-*all.jar [-ed] text` + */ + @JvmStatic + fun main(args: Array) { + try { + val result = processMain(args) + if (result.status == 1) { + System.err.println(result.output) + } else { + println(result.output) + } + exitProcess(result.status) + } catch (e: IllegalArgumentException) { + System.err.println("${UrlEncoder::class.java.simpleName}: ${e.message}") + exitProcess(1) + } + } + + internal data class MainResult(var output: String = usage, var status: Int = 1) + + internal fun processMain(args: Array): MainResult { + val result = MainResult() + if (args.isNotEmpty() && args[0].isNotEmpty()) { + val hasDecode = (args[0] == "-d") + val hasOption = (hasDecode || args[0] == "-e") + if (hasOption && args.size == 2 || !hasOption && args.size == 1) { + val arg = if (hasOption) args[1] else args[0] + if (hasDecode) { + result.output = decode(arg) + } else { + result.output = UrlEncoderUtil.encode(arg) + } + result.status = 0 + } + } + return result + } + + /** + * Transforms a provided [String] into a new string, containing decoded URL characters in the UTF-8 + * encoding. + */ + @JvmStatic + @JvmOverloads + fun decode(source: String, plusToSpace: Boolean = false): String = + // delegate to UrlEncoderFunctions for backwards compatibility + UrlEncoderUtil.decode(source, plusToSpace) + + /** + * Transforms a provided [String] object into a new string, containing only valid URL characters in the UTF-8 + * encoding. + * + * - Letters, numbers, unreserved (`_-!.'()*`) and allowed characters are left intact. + */ + @JvmStatic + @JvmOverloads + fun encode(source: String, allow: String = "", spaceToPlus: Boolean = false): String = + UrlEncoderUtil.encode(source, allow, spaceToPlus) +} diff --git a/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt b/app/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt similarity index 64% rename from lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt rename to app/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt index 8439010..08e78b7 100644 --- a/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt +++ b/app/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderTest.kt @@ -17,14 +17,10 @@ package net.thauvin.erik.urlencoder -import net.thauvin.erik.urlencoder.UrlEncoder.decode -import net.thauvin.erik.urlencoder.UrlEncoder.encode import net.thauvin.erik.urlencoder.UrlEncoder.processMain import net.thauvin.erik.urlencoder.UrlEncoder.usage import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments @@ -34,7 +30,6 @@ import org.junit.jupiter.params.provider.ValueSource import java.util.stream.Stream class UrlEncoderTest { - private val same = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_." companion object { @JvmStatic @@ -54,67 +49,6 @@ class UrlEncoderTest { ) } - @ParameterizedTest(name = "decode({0}) should be {1}") - @MethodSource("validMap") - fun `Decode URL`(expected: String, source: String) { - assertEquals(expected, decode(source)) - } - - @ParameterizedTest(name = "decode({0})") - @MethodSource("invalid") - fun `Decode with Exception`(source: String) { - assertThrows(IllegalArgumentException::class.java, { decode(source) }, "decode($source)") - } - - @Test - fun `Decode when None needed`() { - assertSame(same, decode(same)) - assertEquals("", decode(""), "decode('')") - assertEquals(" ", decode(" "), "decode(' ')") - } - - @Test - fun `Decode with Plus to Space`() { - assertEquals("foo bar", decode("foo+bar", true)) - assertEquals("foo bar foo", decode("foo+bar++foo", true)) - assertEquals("foo bar foo", decode("foo+%20bar%20+foo", true)) - assertEquals("foo + bar", decode("foo+%2B+bar", plusToSpace = true)) - assertEquals("foo+bar", decode("foo%2Bbar", plusToSpace = true)) - } - - @ParameterizedTest(name = "encode({0}) should be {1}") - @MethodSource("validMap") - fun `Encode URL`(source: String, expected: String) { - assertEquals(expected, encode(source)) - } - - @Test - fun `Encode Empty or Blank`() { - assertTrue(encode("", allow = "").isEmpty(), "encode('','')") - assertEquals("", encode(""), "encode('')") - assertEquals("%20", encode(" "), "encode(' ')") - } - - @Test - fun `Encode when None needed`() { - assertSame(same, encode(same)) - assertSame(same, encode(same, allow = ""), "with empty allow") - } - - @Test - fun `Encode with Allow Arg`() { - assertEquals("?test=a%20test", encode("?test=a test", allow = "=?"), "encode(x, =?)") - assertEquals("aaa", encode("aaa", "a"), "encode(aaa, a)") - assertEquals(" ", encode(" ", " "), "encode(' ', ' ')") - } - - @Test - fun `Encode with Space to Plus`() { - assertEquals("foo+bar", encode("foo bar", spaceToPlus = true)) - assertEquals("foo+bar++foo", encode("foo bar foo", spaceToPlus = true)) - assertEquals("foo bar", encode("foo bar", " ", true)) - } - @ParameterizedTest(name = "processMain(-d {1}) should be {0}") @MethodSource("validMap") fun `Main Decode`(expected: String, source: String) { diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index eb0edae..7089640 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -1,17 +1,20 @@ +import buildsrc.utils.Rife2TestListener +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { buildsrc.conventions.lang.`kotlin-jvm` buildsrc.conventions.publishing - id("application") id("com.github.ben-manes.versions") } description = "A simple defensive library to encode/decode URL components" val deployDir = project.layout.projectDirectory.dir("deploy") -val urlEncoderMainClass = "net.thauvin.erik.urlencoder.UrlEncoder" +val mainClassName = "net.thauvin.erik.urlencoder.UrlEncoder" dependencies { // testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") @@ -22,37 +25,37 @@ base { archivesName.set(rootProject.name) } -application { - mainClass.set(urlEncoderMainClass) +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + withSourcesJar() } tasks { - jar { - manifest { - attributes["Main-Class"] = urlEncoderMainClass + withType().configureEach { + kotlinOptions.jvmTarget = java.targetCompatibility.toString() + } + + test { + addTestListener(Rife2TestListener(project.properties["testsBadgeApiKey"]?.toString())) + } + + withType().configureEach { + useJUnitPlatform() + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) } } - val fatJar by registering(Jar::class) { - group = LifecycleBasePlugin.BUILD_GROUP - dependsOn.addAll(listOf("compileJava", "compileKotlin", "processResources")) - archiveClassifier.set("all") - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - manifest { attributes(mapOf("Main-Class" to application.mainClass)) } - val sourcesMain = sourceSets.main.get() - val contents = configurations.runtimeClasspath.get() - .map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output - from(contents) - } - - build { - dependsOn(fatJar) - } - withType().configureEach { destination = file("$projectDir/pom.xml") } + clean { + delete(deployDir) + } + withType().configureEach { dokkaSourceSets { named("main") { @@ -77,8 +80,8 @@ tasks { dependsOn(build, copyToDeploy) } - clean { - delete(deployDir) + "sonar" { + dependsOn(koverReport) } } @@ -86,7 +89,7 @@ publishing { publications { create("mavenJava") { from(components["java"]) - artifactId = rootProject.name + artifactId = "${rootProject.name}-lib" artifact(tasks.javadocJar) } } diff --git a/lib/detekt-baseline.xml b/lib/detekt-baseline.xml index 700d787..2562c74 100644 --- a/lib/detekt-baseline.xml +++ b/lib/detekt-baseline.xml @@ -2,14 +2,13 @@ - ComplexCondition:UrlEncoder.kt$UrlEncoder$hasOption && args.size == 2 || !hasOption && args.size == 1 - MagicNumber:UrlEncoder.kt$UrlEncoder$0x80 - MagicNumber:UrlEncoder.kt$UrlEncoder$0xFF - MagicNumber:UrlEncoder.kt$UrlEncoder$16 - MagicNumber:UrlEncoder.kt$UrlEncoder$3 - MagicNumber:UrlEncoder.kt$UrlEncoder$4 - MaxLineLength:UrlEncoder.kt$UrlEncoder$* - NestedBlockDepth:UrlEncoder.kt$UrlEncoder$@JvmStatic @JvmOverloads fun decode(source: String, plusToSpace: Boolean = false): String - NestedBlockDepth:UrlEncoder.kt$UrlEncoder$@JvmStatic @JvmOverloads fun encode(source: String, allow: String = "", spaceToPlus: Boolean = false): String + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$0x80 + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$0xFF + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$16 + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$3 + MagicNumber:UrlEncoderUtil.kt$UrlEncoderUtil$4 + MaxLineLength:UrlEncoderUtil.kt$UrlEncoderUtil$* + NestedBlockDepth:UrlEncoderUtil.kt$UrlEncoderUtil$@JvmStatic @JvmOverloads fun decode(source: String, plusToSpace: Boolean = false): String + NestedBlockDepth:UrlEncoderUtil.kt$UrlEncoderUtil$@JvmStatic @JvmOverloads fun encode(source: String, allow: String = "", spaceToPlus: Boolean = false): String diff --git a/lib/pom.xml b/lib/pom.xml index 8af3e06..9e38022 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -6,9 +6,9 @@ 4.0.0 - net.thauvin.erik - urlencoder - 1.3.1-SNAPSHOT + urlencoder + urlencoder-lib + unspecified UrlEncoder for Kotlin A simple defensive library to encode/decode URL components https://github.com/ethauvin/urlencoder diff --git a/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt b/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtil.kt similarity index 80% rename from lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt rename to lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtil.kt index 8636b28..0bea939 100644 --- a/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoder.kt +++ b/lib/src/main/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtil.kt @@ -19,7 +19,6 @@ package net.thauvin.erik.urlencoder import java.nio.charset.StandardCharsets import java.util.BitSet -import kotlin.system.exitProcess /** * Most defensive approach to URL encoding and decoding. @@ -37,13 +36,8 @@ import kotlin.system.exitProcess * @author Geert Bevin (gbevin(remove) at uwyn dot com) * @author Erik C. Thauvin (erik@thauvin.net) **/ -object UrlEncoder { +object UrlEncoderUtil { private val hexDigits = "0123456789ABCDEF".toCharArray() - internal val usage = - "Usage : java -jar urlencoder-*all.jar [-ed] text" + System.lineSeparator() + - "Encode and decode URL components defensively." + System.lineSeparator() + - " -e encode (default) " + System.lineSeparator() + - " -d decode" // see https://www.rfc-editor.org/rfc/rfc3986#page-13 // and https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set @@ -196,45 +190,4 @@ object UrlEncoder { return out?.toString() ?: source } - - /** - * Encodes and decodes URLs from the command line. - * - * - `java -jar urlencoder-*all.jar [-ed] text` - */ - @JvmStatic - fun main(args: Array) { - try { - val result = processMain(args) - if (result.status == 1) { - System.err.println(result.output) - } else { - println(result.output) - } - exitProcess(result.status) - } catch (e: IllegalArgumentException) { - System.err.println("${UrlEncoder::class.java.simpleName}: ${e.message}") - exitProcess(1) - } - } - - internal data class MainResult(var output: String = usage, var status: Int = 1) - - internal fun processMain(args: Array): MainResult { - val result = MainResult() - if (args.isNotEmpty() && args[0].isNotEmpty()) { - val hasDecode = (args[0] == "-d") - val hasOption = (hasDecode || args[0] == "-e") - if (hasOption && args.size == 2 || !hasOption && args.size == 1) { - val arg = if (hasOption) args[1] else args[0] - if (hasDecode) { - result.output = decode(arg) - } else { - result.output = encode(arg) - } - result.status = 0 - } - } - return result - } } diff --git a/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt b/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt new file mode 100644 index 0000000..20d8e4d --- /dev/null +++ b/lib/src/test/kotlin/net/thauvin/erik/urlencoder/UrlEncoderUtilTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * 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 net.thauvin.erik.urlencoder + +import net.thauvin.erik.urlencoder.UrlEncoderUtil.decode +import net.thauvin.erik.urlencoder.UrlEncoderUtil.encode +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertSame +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +class UrlEncoderUtilTest { + private val same = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_." + + companion object { + @JvmStatic + fun invalid() = arrayOf("sdkjfh%", "sdkjfh%6", "sdkjfh%xx", "sdfjfh%-1") + + @JvmStatic + fun validMap(): Stream = Stream.of( + arguments("a test &", "a%20test%20%26"), + arguments( + "!abcdefghijklmnopqrstuvwxyz%%ABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_.~=", + "%21abcdefghijklmnopqrstuvwxyz%25%25ABCDEFGHIJKLMNOPQRSTUVQXYZ0123456789-_.%7E%3D" + ), + arguments("%#okékÉȢ smile!😁", "%25%23ok%C3%A9k%C3%89%C8%A2%20smile%21%F0%9F%98%81"), + arguments( + "\uD808\uDC00\uD809\uDD00\uD808\uDF00\uD808\uDD00", "%F0%92%80%80%F0%92%94%80%F0%92%8C%80%F0%92%84%80" + ) + ) + } + + @ParameterizedTest(name = "decode({0}) should be {1}") + @MethodSource("validMap") + fun `Decode URL`(expected: String, source: String) { + assertEquals(expected, decode(source)) + } + + @ParameterizedTest(name = "decode({0})") + @MethodSource("invalid") + fun `Decode with Exception`(source: String) { + assertThrows(IllegalArgumentException::class.java, { decode(source) }, "decode($source)") + } + + @Test + fun `Decode when None needed`() { + assertSame(same, decode(same)) + assertEquals("", decode(""), "decode('')") + assertEquals(" ", decode(" "), "decode(' ')") + } + + @Test + fun `Decode with Plus to Space`() { + assertEquals("foo bar", decode("foo+bar", true)) + assertEquals("foo bar foo", decode("foo+bar++foo", true)) + assertEquals("foo bar foo", decode("foo+%20bar%20+foo", true)) + assertEquals("foo + bar", decode("foo+%2B+bar", plusToSpace = true)) + assertEquals("foo+bar", decode("foo%2Bbar", plusToSpace = true)) + } + + @ParameterizedTest(name = "encode({0}) should be {1}") + @MethodSource("validMap") + fun `Encode URL`(source: String, expected: String) { + assertEquals(expected, encode(source)) + } + + @Test + fun `Encode Empty or Blank`() { + assertTrue(encode("", allow = "").isEmpty(), "encode('','')") + assertEquals("", encode(""), "encode('')") + assertEquals("%20", encode(" "), "encode(' ')") + } + + @Test + fun `Encode when None needed`() { + assertSame(same, encode(same)) + assertSame(same, encode(same, allow = ""), "with empty allow") + } + + @Test + fun `Encode with Allow Arg`() { + assertEquals("?test=a%20test", encode("?test=a test", allow = "=?"), "encode(x, =?)") + assertEquals("aaa", encode("aaa", "a"), "encode(aaa, a)") + assertEquals(" ", encode(" ", " "), "encode(' ', ' ')") + } + + @Test + fun `Encode with Space to Plus`() { + assertEquals("foo+bar", encode("foo bar", spaceToPlus = true)) + assertEquals("foo+bar++foo", encode("foo bar foo", spaceToPlus = true)) + assertEquals("foo bar", encode("foo bar", " ", true)) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e75db1d..86af703 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,9 @@ dependencyResolutionManagement { } } +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + include( + ":app", ":lib", ) From ae8ffb91bb3aecbd6eac0810f3af98026cc805a5 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 23:24:41 +0200 Subject: [PATCH 02/21] tidy up build config files --- app/build.gradle.kts | 47 +++++++------------------------------------- lib/build.gradle.kts | 46 +++++++++++++++++-------------------------- 2 files changed, 25 insertions(+), 68 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b2a918..5797ed2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,30 +15,19 @@ * limitations under the License. */ -import buildsrc.utils.Rife2TestListener -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent import org.jetbrains.dokka.gradle.DokkaTask -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { + buildsrc.conventions.lang.`kotlin-jvm` + buildsrc.conventions.publishing id("application") id("com.github.ben-manes.versions") - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.kotlin.jvm") - id("org.jetbrains.kotlinx.kover") - - buildsrc.conventions.publishing - buildsrc.conventions.sonarqube } -val mavenName = "UrlEncoder" +description = "A simple defensive application to encode/decode URL components" + val deployDir = project.layout.projectDirectory.dir("deploy") -val gitHub = "ethauvin/${rootProject.name}" -val mavenUrl = "https://github.com/$gitHub" -val publicationName = "mavenJava" -val myClassName = "$group.${rootProject.name}.$mavenName" +val mainClassName = "net.thauvin.erik.urlencoder.UrlEncoder" dependencies { implementation(projects.lib) @@ -60,7 +49,6 @@ application { mainClass.set(myClassName) } - tasks { jar { manifest { @@ -84,22 +72,6 @@ tasks { dependsOn(fatJar) } - withType().configureEach { - kotlinOptions.jvmTarget = java.targetCompatibility.toString() - } - - test { - addTestListener(Rife2TestListener(project.properties["testsBadgeApiKey"]?.toString())) - } - - withType().configureEach { - useJUnitPlatform() - testLogging { - exceptionFormat = TestExceptionFormat.FULL - events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) - } - } - withType().configureEach { destination = file("$projectDir/pom.xml") } @@ -117,6 +89,7 @@ tasks { } val copyToDeploy by registering(Sync::class) { + group = PublishingPlugin.PUBLISH_TASK_GROUP from(configurations.runtimeClasspath) { exclude("annotations-*.jar") } @@ -127,13 +100,7 @@ tasks { register("deploy") { description = "Copies all needed files to the 'deploy' directory." group = PublishingPlugin.PUBLISH_TASK_GROUP - dependsOn(build, jar) - outputs.dir(deployDir) - inputs.files(copyToDeploy) - } - - "sonar" { - dependsOn(koverReport) + dependsOn(build, copyToDeploy) } } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 7089640..ecb7ac2 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -1,9 +1,21 @@ -import buildsrc.utils.Rife2TestListener -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent -import org.jetbrains.dokka.gradle.DokkaTask -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +/* + * Copyright 2001-2023 Geert Bevin (gbevin[remove] at uwyn dot com) + * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net) + * + * 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. + */ +import org.jetbrains.dokka.gradle.DokkaTask plugins { buildsrc.conventions.lang.`kotlin-jvm` @@ -14,7 +26,6 @@ plugins { description = "A simple defensive library to encode/decode URL components" val deployDir = project.layout.projectDirectory.dir("deploy") -val mainClassName = "net.thauvin.erik.urlencoder.UrlEncoder" dependencies { // testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") @@ -32,22 +43,6 @@ java { } tasks { - withType().configureEach { - kotlinOptions.jvmTarget = java.targetCompatibility.toString() - } - - test { - addTestListener(Rife2TestListener(project.properties["testsBadgeApiKey"]?.toString())) - } - - withType().configureEach { - useJUnitPlatform() - testLogging { - exceptionFormat = TestExceptionFormat.FULL - events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) - } - } - withType().configureEach { destination = file("$projectDir/pom.xml") } @@ -59,13 +54,12 @@ tasks { withType().configureEach { dokkaSourceSets { named("main") { - moduleName.set("UrlEncoder API") + moduleName.set("UrlEncoder Library") } } } val copyToDeploy by registering(Sync::class) { - description = "Copies all needed files to the 'deploy' directory." group = PublishingPlugin.PUBLISH_TASK_GROUP from(configurations.runtimeClasspath) { exclude("annotations-*.jar") @@ -79,10 +73,6 @@ tasks { group = PublishingPlugin.PUBLISH_TASK_GROUP dependsOn(build, copyToDeploy) } - - "sonar" { - dependsOn(koverReport) - } } publishing { From 1b401459709655dd3d81f934e1a3a36b6d42e7cb Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 23:26:04 +0200 Subject: [PATCH 03/21] fix main class val --- app/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5797ed2..6555d51 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -27,7 +27,7 @@ plugins { description = "A simple defensive application to encode/decode URL components" val deployDir = project.layout.projectDirectory.dir("deploy") -val mainClassName = "net.thauvin.erik.urlencoder.UrlEncoder" +val urlEncoderMainClass = "net.thauvin.erik.urlencoder.UrlEncoder" dependencies { implementation(projects.lib) @@ -46,13 +46,13 @@ java { } application { - mainClass.set(myClassName) + mainClass.set(urlEncoderMainClass) } tasks { jar { manifest { - attributes["Main-Class"] = myClassName + attributes["Main-Class"] = urlEncoderMainClass } } From fc3a5648edd0bf99738d4fa80afffab6a1aaa14f Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 23:27:56 +0200 Subject: [PATCH 04/21] tidy build config, update pom.xmls --- app/build.gradle.kts | 6 ------ app/pom.xml | 9 +++++---- lib/build.gradle.kts | 6 ------ lib/pom.xml | 4 ++-- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6555d51..a628ef8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,12 +39,6 @@ base { archivesName.set(rootProject.name) } -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - withSourcesJar() -} - application { mainClass.set(urlEncoderMainClass) } diff --git a/app/pom.xml b/app/pom.xml index 57aff04..e610169 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -6,10 +6,11 @@ 4.0.0 - urlencoder + net.thauvin.erik urlencoder - unspecified + 1.3.1-SNAPSHOT UrlEncoder for Kotlin + A simple defensive application to encode/decode URL components https://github.com/ethauvin/urlencoder @@ -48,9 +49,9 @@ compile - urlencoder + net.thauvin.erik urlencoder-lib - unspecified + 1.3.1-SNAPSHOT runtime diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index ecb7ac2..adb85c9 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -36,12 +36,6 @@ base { archivesName.set(rootProject.name) } -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - withSourcesJar() -} - tasks { withType().configureEach { destination = file("$projectDir/pom.xml") diff --git a/lib/pom.xml b/lib/pom.xml index 9e38022..a586d68 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -6,9 +6,9 @@ 4.0.0 - urlencoder + net.thauvin.erik urlencoder-lib - unspecified + 1.3.1-SNAPSHOT UrlEncoder for Kotlin A simple defensive library to encode/decode URL components https://github.com/ethauvin/urlencoder From 62cc1106511fff2be2337acaaeeb593009c65998 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 23:30:51 +0200 Subject: [PATCH 05/21] fix base archive name by adding `-lib` suffix --- lib/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index adb85c9..832d5b9 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { } base { - archivesName.set(rootProject.name) + archivesName.set("${rootProject.name}-lib") } tasks { From 12c1c4f12a828c8752b33936da2bb13a8a405680 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 31 May 2023 23:46:57 +0200 Subject: [PATCH 06/21] tidy fatJar task --- app/build.gradle.kts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a628ef8..8c24b9e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,16 +50,17 @@ tasks { } } - val fatJar = register("fatJar") { - group = "build" + val fatJar by registering(Jar::class) { + group = LifecycleBasePlugin.BUILD_GROUP dependsOn.addAll(listOf("compileJava", "compileKotlin", "processResources")) archiveClassifier.set("all") duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes(mapOf("Main-Class" to application.mainClass)) } - val sourcesMain = sourceSets.main.get() - val contents = configurations.runtimeClasspath.get() - .map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output - from(contents) + from(sourceSets.main.get().output) + dependsOn(configurations.runtimeClasspath) + from(configurations.runtimeClasspath.map { classpath -> + classpath.incoming.artifacts.artifactFiles.files.filter { it.name.endsWith("jar") }.map { zipTree(it) } + }) } build { From 78403becf29386ece5f2101b140f31f8a48776b0 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 08:49:55 +0200 Subject: [PATCH 07/21] refactor sonar build config --- build.gradle.kts | 6 ++++++ buildSrc/build.gradle.kts | 2 +- .../conventions/lang/kotlin-jvm.gradle.kts | 15 +++++++++------ .../lang/kotlin-multiplatform-base.gradle.kts | 14 ++++++++++++++ .../buildsrc/conventions/publishing.gradle.kts | 2 +- ...de-quality.gradle.kts => sonarqube.gradle.kts} | 10 ---------- 6 files changed, 31 insertions(+), 18 deletions(-) rename buildSrc/src/main/kotlin/buildsrc/conventions/{code-quality.gradle.kts => sonarqube.gradle.kts} (80%) diff --git a/build.gradle.kts b/build.gradle.kts index 9eae0de..6f48859 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,13 @@ plugins { buildsrc.conventions.base + buildsrc.conventions.sonarqube } group = "net.thauvin.erik" version = "1.4.0-SNAPSHOT" + +dependencies { + kover(projects.app) + kover(projects.lib) +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index e862322..0b4b926 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,4 +9,4 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21") implementation("org.jetbrains.kotlinx:kover-gradle-plugin:0.7.0") implementation("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.2.0.3129") -} \ No newline at end of file +} diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts index 790e694..eb302e8 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts @@ -1,15 +1,10 @@ package buildsrc.conventions.lang import buildsrc.utils.Rife2TestListener -import org.gradle.api.JavaVersion -import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent -import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.sonarqube.gradle.SonarTask /** * Common configuration for Kotlin/JVM projects @@ -20,7 +15,8 @@ import org.sonarqube.gradle.SonarTask plugins { id("buildsrc.conventions.base") kotlin("jvm") - id("buildsrc.conventions.code-quality") + id("io.gitlab.arturbosch.detekt") + id("org.jetbrains.kotlinx.kover") } java { @@ -39,4 +35,11 @@ tasks.withType().configureEach { tasks.withType().configureEach { useJUnitPlatform() + + val testsBadgeApiKey = providers.gradleProperty("testsBadgeApiKey") + addTestListener(Rife2TestListener(testsBadgeApiKey)) + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } } diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts index 4af978b..3a98de0 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts @@ -1,5 +1,8 @@ package buildsrc.conventions.lang +import buildsrc.utils.Rife2TestListener +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget @@ -13,6 +16,8 @@ import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget plugins { id("buildsrc.conventions.base") kotlin("multiplatform") + id("io.gitlab.arturbosch.detekt") + id("org.jetbrains.kotlinx.kover") } @@ -43,3 +48,12 @@ kotlin { } } } + +tasks.withType().configureEach { + val testsBadgeApiKey = providers.gradleProperty("testsBadgeApiKey") + addTestListener(Rife2TestListener(testsBadgeApiKey)) + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } +} diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts index 2390479..45308aa 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts @@ -36,7 +36,7 @@ publishing { licenses { license { name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") } } developers { diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/code-quality.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts similarity index 80% rename from buildSrc/src/main/kotlin/buildsrc/conventions/code-quality.gradle.kts rename to buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index f9c3d13..8cfef09 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/code-quality.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -24,7 +24,6 @@ import org.sonarqube.gradle.SonarTask plugins { id("org.sonarqube") - id("io.gitlab.arturbosch.detekt") id("org.jetbrains.kotlinx.kover") } @@ -42,12 +41,3 @@ sonarqube { tasks.withType().configureEach { dependsOn(tasks.matching { it.name == "koverXmlReport" }) } - -tasks.withType().configureEach { - val testsBadgeApiKey = providers.gradleProperty("testsBadgeApiKey") - addTestListener(Rife2TestListener(testsBadgeApiKey)) - testLogging { - exceptionFormat = TestExceptionFormat.FULL - events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) - } -} From 7ddfa061c6e4ea464bf612fe4ecd697b575100b5 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 08:56:43 +0200 Subject: [PATCH 08/21] tidy up/add some docs --- .../buildsrc/conventions/publishing.gradle.kts | 3 +++ .../buildsrc/conventions/sonarqube.gradle.kts | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts index 45308aa..9155ad4 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/publishing.gradle.kts @@ -84,6 +84,8 @@ signing { sign(publishing.publications) setRequired({ + // only enable signing for non-snapshot versions, or when publishing to a non-local repo, otherwise + // publishing to Maven Local requires signing for users without access to the signing key. !isSnapshotVersion() || gradle.taskGraph.hasTask("publish") }) } @@ -99,6 +101,7 @@ tasks.withType().configureEach { } val javadocJar by tasks.registering(Jar::class) { + description = "Generate Javadoc using Dokka" dependsOn(tasks.dokkaJavadoc) from(tasks.dokkaJavadoc) archiveClassifier.set("javadoc") diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index 8cfef09..32d270b 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -17,16 +17,27 @@ package buildsrc.conventions -import buildsrc.utils.Rife2TestListener -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent import org.sonarqube.gradle.SonarTask +/** + * Convention plugin for SonarQube analysis. + * + * SonarQube depends on an aggregated XML coverage report from + * [Kotlinx Kover](https://github.com/Kotlin/kotlinx-kover). + * See the Kover docs for + * [how to aggregate coverage reports](https://kotlin.github.io/kotlinx-kover/gradle-plugin/#multiproject-build). + */ + plugins { id("org.sonarqube") id("org.jetbrains.kotlinx.kover") } +if (project != rootProject) { + logger.warn("The SonarQube convention plugin should only be applied to the root project") + // https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner-for-gradle/#analyzing-multi-project-builds +} + sonarqube { properties { property("sonar.projectName", rootProject.name) @@ -39,5 +50,6 @@ sonarqube { } tasks.withType().configureEach { + // workaround for https://github.com/Kotlin/kotlinx-kover/issues/394 dependsOn(tasks.matching { it.name == "koverXmlReport" }) } From e53dc8c546affcc918cc081842b92bdd478337cf Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:13:17 +0200 Subject: [PATCH 09/21] only run sonar task on the root project --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 87a9233..db13add 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,4 +46,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew sonar --info + run: ./gradlew :sonar --info From bccfd0003fa3a47f7b475cceba97bbb70449fda5 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:15:31 +0200 Subject: [PATCH 10/21] enable sonarqube on Kotlin projects --- .../main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts | 1 + .../conventions/lang/kotlin-multiplatform-base.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts index eb302e8..550ce3a 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts @@ -17,6 +17,7 @@ plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") id("org.jetbrains.kotlinx.kover") + id("org.sonarqube") } java { diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts index 3a98de0..547d24e 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts @@ -18,6 +18,7 @@ plugins { kotlin("multiplatform") id("io.gitlab.arturbosch.detekt") id("org.jetbrains.kotlinx.kover") + id("org.sonarqube") } From 86ae66178803227d1b0fafc8a5d054176144e58e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:22:48 +0200 Subject: [PATCH 11/21] enable verbose sonar to try and help debugging --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index db13add..9c25062 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,4 +46,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew :sonar --info + run: ./gradlew :sonar --info -Dsonar.verbose=true From 13c73903c3aa10dd694ba5b382dd2a2697002cf0 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:33:55 +0200 Subject: [PATCH 12/21] only enable SonarQube in :app --- .github/workflows/gradle.yml | 2 +- app/build.gradle.kts | 3 +++ .../conventions/lang/kotlin-multiplatform-base.gradle.kts | 1 - .../main/kotlin/buildsrc/conventions/sonarqube.gradle.kts | 8 ++++---- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9c25062..bce879c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,4 +46,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew :sonar --info -Dsonar.verbose=true + run: ./gradlew :app:sonar --info -Dsonar.verbose=true diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8c24b9e..53beb10 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,7 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { buildsrc.conventions.lang.`kotlin-jvm` buildsrc.conventions.publishing + buildsrc.conventions.sonarqube id("application") id("com.github.ben-manes.versions") } @@ -31,6 +32,8 @@ val urlEncoderMainClass = "net.thauvin.erik.urlencoder.UrlEncoder" dependencies { implementation(projects.lib) + kover(projects.lib) + // testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") } diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts index 547d24e..3a98de0 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-multiplatform-base.gradle.kts @@ -18,7 +18,6 @@ plugins { kotlin("multiplatform") id("io.gitlab.arturbosch.detekt") id("org.jetbrains.kotlinx.kover") - id("org.sonarqube") } diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index 32d270b..13650f3 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -33,10 +33,10 @@ plugins { id("org.jetbrains.kotlinx.kover") } -if (project != rootProject) { - logger.warn("The SonarQube convention plugin should only be applied to the root project") - // https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner-for-gradle/#analyzing-multi-project-builds -} +//if (project != rootProject) { +// logger.warn("The SonarQube convention plugin should only be applied to the root project") +// // https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner-for-gradle/#analyzing-multi-project-builds +//} sonarqube { properties { From 09a58f41422322638dcf400d2ff63fbcbcaf7dee Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:40:15 +0200 Subject: [PATCH 13/21] remove SonarQube from Kotlin/JVM convention --- .../kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts | 1 - .../main/kotlin/buildsrc/conventions/sonarqube.gradle.kts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts index 550ce3a..eb302e8 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/lang/kotlin-jvm.gradle.kts @@ -17,7 +17,6 @@ plugins { kotlin("jvm") id("io.gitlab.arturbosch.detekt") id("org.jetbrains.kotlinx.kover") - id("org.sonarqube") } java { diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index 13650f3..14c29ec 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -33,11 +33,6 @@ plugins { id("org.jetbrains.kotlinx.kover") } -//if (project != rootProject) { -// logger.warn("The SonarQube convention plugin should only be applied to the root project") -// // https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner-for-gradle/#analyzing-multi-project-builds -//} - sonarqube { properties { property("sonar.projectName", rootProject.name) From 3b6fffdc5228bfcb220623b5737f262512955170 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 2 Jun 2023 09:43:43 +0200 Subject: [PATCH 14/21] remove Kover & Sonar from base project --- build.gradle.kts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6f48859..9eae0de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,13 +17,7 @@ plugins { buildsrc.conventions.base - buildsrc.conventions.sonarqube } group = "net.thauvin.erik" version = "1.4.0-SNAPSHOT" - -dependencies { - kover(projects.app) - kover(projects.lib) -} From 76750cfd718acbf80a87052ab10957484ae404ae Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 16:38:17 +0200 Subject: [PATCH 15/21] try adding sonar to the root project again... --- .github/workflows/gradle.yml | 2 +- build.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index bce879c..8a90dc0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,4 +46,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew :app:sonar --info -Dsonar.verbose=true + run: ./gradlew sonar --info -Dsonar.verbose=true diff --git a/build.gradle.kts b/build.gradle.kts index 9eae0de..3636913 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,7 @@ plugins { buildsrc.conventions.base + buildsrc.conventions.sonarqube } group = "net.thauvin.erik" From 97a23a195fcd431ac2d9853828aad903b32cc04d Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 16:57:29 +0200 Subject: [PATCH 16/21] try skipping projects... --- app/build.gradle.kts | 4 ++++ lib/build.gradle.kts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 53beb10..f7d9ea5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,3 +111,7 @@ publishing { } } } + +sonar { + isSkipProject = true +} diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 832d5b9..cdf3f7b 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -20,6 +20,7 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { buildsrc.conventions.lang.`kotlin-jvm` buildsrc.conventions.publishing + buildsrc.conventions.sonarqube id("com.github.ben-manes.versions") } @@ -78,3 +79,7 @@ publishing { } } } + +sonar { + isSkipProject = true +} From c1efd8a95556a74cddd06d9871c64b05086f7873 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 17:18:29 +0200 Subject: [PATCH 17/21] don't skip :lib? --- lib/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index cdf3f7b..3f14a3b 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -81,5 +81,5 @@ publishing { } sonar { - isSkipProject = true +// isSkipProject = true } From f1622d0c0aa83c0b63ce8d7d21f55296674e9a80 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 17:24:12 +0200 Subject: [PATCH 18/21] try only setting sonar properties on the root project --- build.gradle.kts | 11 ++++++++++ .../buildsrc/conventions/sonarqube.gradle.kts | 22 +++++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3636913..a08d5ee 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,3 +22,14 @@ plugins { group = "net.thauvin.erik" version = "1.4.0-SNAPSHOT" + +sonarqube { + properties { + property("sonar.projectName", rootProject.name) + property("sonar.projectKey", "ethauvin_${rootProject.name}") + property("sonar.organization", "ethauvin-github") + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.sourceEncoding", "UTF-8") + property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") + } +} diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index 14c29ec..eae56e9 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -32,17 +32,17 @@ plugins { id("org.sonarqube") id("org.jetbrains.kotlinx.kover") } - -sonarqube { - properties { - property("sonar.projectName", rootProject.name) - property("sonar.projectKey", "ethauvin_${rootProject.name}") - property("sonar.organization", "ethauvin-github") - property("sonar.host.url", "https://sonarcloud.io") - property("sonar.sourceEncoding", "UTF-8") - property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") - } -} +// +//sonarqube { +// properties { +// property("sonar.projectName", rootProject.name) +// property("sonar.projectKey", "ethauvin_${rootProject.name}") +// property("sonar.organization", "ethauvin-github") +// property("sonar.host.url", "https://sonarcloud.io") +// property("sonar.sourceEncoding", "UTF-8") +// property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") +// } +//} tasks.withType().configureEach { // workaround for https://github.com/Kotlin/kotlinx-kover/issues/394 From 1986a8e56d85be470bbdb0fadcf0b6a41513fefc Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 17:47:18 +0200 Subject: [PATCH 19/21] try only adding sonar to :lib --- app/build.gradle.kts | 5 ----- build.gradle.kts | 11 ---------- .../buildsrc/conventions/sonarqube.gradle.kts | 22 +++++++++---------- lib/build.gradle.kts | 4 ---- 4 files changed, 11 insertions(+), 31 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f7d9ea5..5529ba5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,6 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { buildsrc.conventions.lang.`kotlin-jvm` buildsrc.conventions.publishing - buildsrc.conventions.sonarqube id("application") id("com.github.ben-manes.versions") } @@ -111,7 +110,3 @@ publishing { } } } - -sonar { - isSkipProject = true -} diff --git a/build.gradle.kts b/build.gradle.kts index a08d5ee..3636913 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,14 +22,3 @@ plugins { group = "net.thauvin.erik" version = "1.4.0-SNAPSHOT" - -sonarqube { - properties { - property("sonar.projectName", rootProject.name) - property("sonar.projectKey", "ethauvin_${rootProject.name}") - property("sonar.organization", "ethauvin-github") - property("sonar.host.url", "https://sonarcloud.io") - property("sonar.sourceEncoding", "UTF-8") - property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") - } -} diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index eae56e9..14c29ec 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -32,17 +32,17 @@ plugins { id("org.sonarqube") id("org.jetbrains.kotlinx.kover") } -// -//sonarqube { -// properties { -// property("sonar.projectName", rootProject.name) -// property("sonar.projectKey", "ethauvin_${rootProject.name}") -// property("sonar.organization", "ethauvin-github") -// property("sonar.host.url", "https://sonarcloud.io") -// property("sonar.sourceEncoding", "UTF-8") -// property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") -// } -//} + +sonarqube { + properties { + property("sonar.projectName", rootProject.name) + property("sonar.projectKey", "ethauvin_${rootProject.name}") + property("sonar.organization", "ethauvin-github") + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.sourceEncoding", "UTF-8") + property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") + } +} tasks.withType().configureEach { // workaround for https://github.com/Kotlin/kotlinx-kover/issues/394 diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 3f14a3b..aeb5eb3 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -79,7 +79,3 @@ publishing { } } } - -sonar { -// isSkipProject = true -} From 9be2d3897e39694d0aab385363a981e3041ad62a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 21:01:41 +0200 Subject: [PATCH 20/21] use `sonar {}` extension instead of `sonarqube {}` --- .../src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts index b5b19e3..06ca6af 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/sonarqube.gradle.kts @@ -33,7 +33,7 @@ plugins { id("org.jetbrains.kotlinx.kover") } -sonarqube { +sonar { properties { property("sonar.projectName", rootProject.name) property("sonar.projectKey", "ethauvin_${rootProject.name}") From 9b34b5684c6649b2228fd594415057561a6f01c5 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 4 Jun 2023 21:07:48 +0200 Subject: [PATCH 21/21] tidy up sonar config --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5529ba5..53beb10 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,7 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { buildsrc.conventions.lang.`kotlin-jvm` buildsrc.conventions.publishing + buildsrc.conventions.sonarqube id("application") id("com.github.ben-manes.versions") }