Reworked Utils.

This commit is contained in:
Erik C. Thauvin 2022-01-22 06:56:54 -08:00
parent aef7d1fa6a
commit 86cc8b1519
7 changed files with 257 additions and 271 deletions

View file

@ -2,12 +2,11 @@
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<CurrentIssues>
<ID>LongMethod:UtilsTest.kt$UtilsTest$@Test fun testSystemProperties()</ID>
<ID>MagicNumber:Utils.kt$Utils$3</ID>
<ID>MagicNumber:Utils.kt$Utils$4</ID>
<ID>MagicNumber:Utils.kt$Utils$5</ID>
<ID>NestedBlockDepth:Utils.kt$Utils$fun loadProperties(file: File): Properties</ID>
<ID>NestedBlockDepth:Utils.kt$Utils$fun parseSemVer(input: String?, version: Version): Boolean</ID>
<ID>NestedBlockDepth:Utils.kt$Utils$fun saveProperties(projectDir: File, config: SemverConfig, version: Version)</ID>
<ID>MagicNumber:Utils.kt$3</ID>
<ID>MagicNumber:Utils.kt$4</ID>
<ID>MagicNumber:Utils.kt$5</ID>
<ID>NestedBlockDepth:Utils.kt$fun File.loadProperties(): Properties</ID>
<ID>NestedBlockDepth:Utils.kt$fun parseSemVer(input: String?, version: Version): Boolean</ID>
<ID>NestedBlockDepth:Utils.kt$fun saveProperties(projectDir: File, config: SemverConfig, version: Version)</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -59,7 +59,7 @@ open class SemverIncrementBuildMetaTask @Inject constructor(
version.buildMeta = buildMeta
project.version = version.semver
if (logger.isLifecycleEnabled) logger.lifecycle("Version: ${project.version}")
Utils.saveProperties(project.projectDir, config, version)
saveProperties(project.projectDir, config, version)
}
}
}

View file

@ -58,6 +58,6 @@ open class SemverIncrementTask @Inject constructor(
)
project.version = version.semver
if (logger.isLifecycleEnabled) logger.lifecycle("Version: ${project.version}")
Utils.saveProperties(project.projectDir, config, version)
saveProperties(project.projectDir, config, version)
}
}

View file

@ -59,7 +59,7 @@ class SemverPlugin : Plugin<Project> {
}
private fun afterEvaluate(project: Project) {
val propsFile = Utils.getPropertiesFile(project.projectDir, config.properties)
val propsFile = getPropertiesFile(project.projectDir, config.properties)
if (project.version != "unspecified" && project.logger.isWarnEnabled) {
project.logger.warn(
@ -77,15 +77,15 @@ class SemverPlugin : Plugin<Project> {
)
}
val props = Utils.loadProperties(this)
val props = this.loadProperties()
val requiredProps = setOf(
config.semverKey, config.majorKey, config.minorKey, config.patchKey,
config.preReleaseKey, config.buildMetaKey
)
val hasReqProps = !isNew && props.stringPropertyNames().containsAll(requiredProps) &&
Utils.isNotSystemProperty(requiredProps)
requiredProps.isNotSystemProperty()
Utils.loadVersion(config, version, props)
loadVersion(config, version, props)
project.tasks.withType(SemverIncrementBuildMetaTask::class.java) {
buildMeta = version.buildMeta
@ -100,7 +100,7 @@ class SemverPlugin : Plugin<Project> {
if (project.logger.isInfoEnabled) {
project.logger.info("[$simpleName] Saving version properties to `$absoluteFile`.")
}
Utils.saveProperties(project.projectDir, config, version)
saveProperties(project.projectDir, config, version)
}
}
}

View file

@ -1,223 +0,0 @@
/*
* Utils.kt
*
* Copyright (c) 2018-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.gradle.semver
import org.gradle.api.GradleException
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.Properties
/**
* The <code>Utils</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-10
* @since 1.0
*/
object Utils {
fun File.canReadFile(): Boolean {
return canRead() && isFile
}
private fun Properties.put(key: String, value: String, isValidCondition: Boolean) {
if (isValidCondition) put(key, value)
}
fun isNotSystemProperty(keys: Set<String>): Boolean {
keys.forEach {
if (System.getProperties().containsKey(it)) return false
}
return true
}
fun getPropertiesFile(projectDir: File, propsFile: String): File {
return if (File(propsFile).isAbsolute) {
File(propsFile)
} else {
File(projectDir, propsFile)
}
}
fun loadProperties(file: File): Properties {
var isNew = false
val props = Properties()
file.apply {
try {
if (!exists() && createNewFile()) {
isNew = true
}
} catch (e: IOException) {
throw GradleException("Unable to create: `$absoluteFile`", e)
}
if (canReadFile()) {
FileInputStream(this).reader().use { reader ->
props.apply {
if (!isNew) {
load(reader)
}
}
}
} else {
throw GradleException("Unable to read version from: `$absoluteFile`")
}
}
return props
}
fun loadIntProperty(props: Properties, key: String, default: Int): Int {
try {
return loadProperty(props, key, default.toString()).toInt()
} catch (e: java.lang.NumberFormatException) {
throw GradleException("Unable to parse $key property. (${e.message})", e)
}
}
fun loadProperty(props: Properties, key: String, default: String): String {
return System.getProperty(key, if (props.isNotEmpty()) props.getProperty(key, default) else default)
}
fun loadVersion(config: SemverConfig, version: Version, props: Properties) {
props.apply {
if (!parseSemVer(System.getProperty(config.semverKey), version)) {
version.major = loadIntProperty(this, config.majorKey, Version.DEFAULT_MAJOR)
version.minor = loadIntProperty(this, config.minorKey, Version.DEFAULT_MINOR)
version.patch = loadIntProperty(this, config.patchKey, Version.DEFAULT_PATCH)
version.preRelease = loadProperty(this, config.preReleaseKey, Version.DEFAULT_EMPTY)
version.buildMeta = loadProperty(this, config.buildMetaKey, Version.DEFAULT_EMPTY)
}
if (!isEmpty) {
version.preReleasePrefix =
getProperty(config.preReleasePrefixKey, Version.DEFAULT_PRERELEASE_PREFIX)
version.buildMetaPrefix =
getProperty(config.buildMetaPrefixKey, Version.DEFAULT_BUILDMETA_PREFIX)
version.separator = getProperty(config.separatorKey, Version.DEFAULT_SEPARATOR)
}
}
}
fun parseSemVer(input: String?, version: Version): Boolean {
if (input.isNullOrBlank()) return false
try {
val max = 5
val min = 3
val parts = input.split(
Regex("[\\Q${version.separator}${version.preReleasePrefix}${version.buildMetaPrefix}\\E]"),
max
)
if (parts.size >= min) {
version.major = parts[0].toInt()
version.minor = parts[1].toInt()
version.patch = parts[2].toInt()
version.preRelease = ""
version.buildMeta = ""
if (parts.size > min) {
when (parts.size) {
max -> {
version.preRelease = parts[3]
version.buildMeta = parts[4]
}
4 -> {
if (input.endsWith(version.buildMetaPrefix + parts[3])) {
version.buildMeta = parts[3]
} else {
version.preRelease = parts[3]
}
}
}
}
} else {
throw NumberFormatException("Not enough parts.")
}
} catch (e: NumberFormatException) {
throw GradleException("Unable to parse version: \"$input\" (${e.message})", e)
}
return true
}
fun saveProperties(projectDir: File, config: SemverConfig, version: Version) {
val propsFile = getPropertiesFile(projectDir, config.properties)
SortedProperties().apply {
try {
propsFile.apply {
if (canReadFile()) {
FileInputStream(this).reader().use { load(it) }
} else {
createNewFile()
}
put(config.semverKey, version.semver)
put(config.majorKey, version.major.toString())
put(config.minorKey, version.minor.toString())
put(config.patchKey, version.patch.toString())
put(config.preReleaseKey, version.preRelease)
put(config.buildMetaKey, version.buildMeta)
put(config.semverKey, version.semver)
put(
config.buildMetaPrefixKey, version.buildMetaPrefix,
version.buildMetaPrefix != Version.DEFAULT_BUILDMETA_PREFIX ||
containsKey(config.buildMetaPrefixKey)
)
put(
config.preReleasePrefixKey, version.preReleasePrefix,
version.preReleasePrefix != Version.DEFAULT_PRERELEASE_PREFIX ||
containsKey(config.preReleasePrefixKey)
)
put(
config.separatorKey, version.separator,
version.separator != Version.DEFAULT_SEPARATOR ||
containsKey(config.separatorKey)
)
if (canWrite()) {
FileOutputStream(this).writer().use {
store(it, "Generated by the Semver Plugin for Gradle")
}
} else {
throw IOException("Can't write.")
}
}
} catch (e: IOException) {
throw GradleException("Unable to write version to: `${propsFile.absoluteFile}`", e)
}
}
}
}

View file

@ -0,0 +1,214 @@
/*
* utils.kt
*
* Copyright (c) 2018-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.gradle.semver
import org.gradle.api.GradleException
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.Properties
private fun Properties.put(key: String, value: String, isValidCondition: Boolean) {
if (isValidCondition) put(key, value)
}
fun File.canReadFile(): Boolean {
return canRead() && isFile
}
fun getPropertiesFile(projectDir: File, propsFile: String): File {
return if (File(propsFile).isAbsolute) {
File(propsFile)
} else {
File(projectDir, propsFile)
}
}
fun Set<String>.isNotSystemProperty(): Boolean {
this.forEach {
if (System.getProperties().containsKey(it)) return false
}
return true
}
fun File.loadProperties(): Properties {
var isNew = false
val props = Properties()
this.apply {
try {
if (!exists() && createNewFile()) {
isNew = true
}
} catch (e: IOException) {
throw GradleException("Unable to create: `$absoluteFile`", e)
}
if (canReadFile()) {
FileInputStream(this).reader().use { reader ->
props.apply {
if (!isNew) {
load(reader)
}
}
}
} else {
throw GradleException("Unable to read version from: `$absoluteFile`")
}
}
return props
}
fun loadIntProperty(props: Properties, key: String, default: Int): Int {
try {
return loadProperty(props, key, default.toString()).toInt()
} catch (e: java.lang.NumberFormatException) {
throw GradleException("Unable to parse $key property. (${e.message})", e)
}
}
fun loadProperty(props: Properties, key: String, default: String): String {
return System.getProperty(key, if (props.isNotEmpty()) props.getProperty(key, default) else default)
}
fun loadVersion(config: SemverConfig, version: Version, props: Properties) {
props.apply {
if (!parseSemVer(System.getProperty(config.semverKey), version)) {
version.major = loadIntProperty(this, config.majorKey, Version.DEFAULT_MAJOR)
version.minor = loadIntProperty(this, config.minorKey, Version.DEFAULT_MINOR)
version.patch = loadIntProperty(this, config.patchKey, Version.DEFAULT_PATCH)
version.preRelease = loadProperty(this, config.preReleaseKey, Version.DEFAULT_EMPTY)
version.buildMeta = loadProperty(this, config.buildMetaKey, Version.DEFAULT_EMPTY)
}
if (!isEmpty) {
version.preReleasePrefix =
getProperty(config.preReleasePrefixKey, Version.DEFAULT_PRERELEASE_PREFIX)
version.buildMetaPrefix =
getProperty(config.buildMetaPrefixKey, Version.DEFAULT_BUILDMETA_PREFIX)
version.separator = getProperty(config.separatorKey, Version.DEFAULT_SEPARATOR)
}
}
}
fun parseSemVer(input: String?, version: Version): Boolean {
if (input.isNullOrBlank()) return false
try {
val max = 5
val min = 3
val parts = input.split(
Regex("[\\Q${version.separator}${version.preReleasePrefix}${version.buildMetaPrefix}\\E]"),
max
)
if (parts.size >= min) {
version.major = parts[0].toInt()
version.minor = parts[1].toInt()
version.patch = parts[2].toInt()
version.preRelease = ""
version.buildMeta = ""
if (parts.size > min) {
when (parts.size) {
max -> {
version.preRelease = parts[3]
version.buildMeta = parts[4]
}
4 -> {
if (input.endsWith(version.buildMetaPrefix + parts[3])) {
version.buildMeta = parts[3]
} else {
version.preRelease = parts[3]
}
}
}
}
} else {
throw NumberFormatException("Not enough parts.")
}
} catch (e: NumberFormatException) {
throw GradleException("Unable to parse version: \"$input\" (${e.message})", e)
}
return true
}
fun saveProperties(projectDir: File, config: SemverConfig, version: Version) {
val propsFile = getPropertiesFile(projectDir, config.properties)
SortedProperties().apply {
try {
propsFile.apply {
if (canReadFile()) {
FileInputStream(this).reader().use { load(it) }
} else {
createNewFile()
}
put(config.semverKey, version.semver)
put(config.majorKey, version.major.toString())
put(config.minorKey, version.minor.toString())
put(config.patchKey, version.patch.toString())
put(config.preReleaseKey, version.preRelease)
put(config.buildMetaKey, version.buildMeta)
put(config.semverKey, version.semver)
put(
config.buildMetaPrefixKey, version.buildMetaPrefix,
version.buildMetaPrefix != Version.DEFAULT_BUILDMETA_PREFIX ||
containsKey(config.buildMetaPrefixKey)
)
put(
config.preReleasePrefixKey, version.preReleasePrefix,
version.preReleasePrefix != Version.DEFAULT_PRERELEASE_PREFIX ||
containsKey(config.preReleasePrefixKey)
)
put(
config.separatorKey, version.separator,
version.separator != Version.DEFAULT_SEPARATOR ||
containsKey(config.separatorKey)
)
if (canWrite()) {
FileOutputStream(this).writer().use {
store(it, "Generated by the Semver Plugin for Gradle")
}
} else {
throw IOException("Can't write.")
}
}
} catch (e: IOException) {
throw GradleException("Unable to write version to: `${propsFile.absoluteFile}`", e)
}
}
}

View file

@ -1,5 +1,5 @@
/*
* UtilsTest.kt
* est.kt
*
* Copyright (c) 2018-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -32,7 +32,6 @@
package net.thauvin.erik.gradle.semver
import net.thauvin.erik.gradle.semver.Utils.canReadFile
import org.gradle.api.GradleException
import java.io.File
import kotlin.test.Test
@ -42,7 +41,7 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
/**
* [Utils] Tests
* Tests
*/
class UtilsTest {
private val version = Version()
@ -53,16 +52,16 @@ class UtilsTest {
@Test
fun testExceptions() {
assertFailsWith<GradleException>("2.1.1a") {
Utils.parseSemVer("2.1.1a", version)
parseSemVer("2.1.1a", version)
}
assertFailsWith<GradleException>("2a.1.1") {
Utils.parseSemVer("2a.1.1", version)
parseSemVer("2a.1.1", version)
}
assertFailsWith<GradleException>("2.1a.1") {
Utils.parseSemVer("2.1a.1", version)
parseSemVer("2.1a.1", version)
}
assertFailsWith<GradleException>("2.1") {
Utils.parseSemVer("2.1", version)
parseSemVer("2.1", version)
}
}
@ -72,7 +71,7 @@ class UtilsTest {
fooDir.mkdir()
val fooFile = File(fooDir, propsFile.name)
config.properties = fooFile.absolutePath
Utils.saveProperties(projectDir, config, version)
saveProperties(projectDir, config, version)
assertEquals(
fooFile.canReadFile(),
@ -80,7 +79,7 @@ class UtilsTest {
"foo properties file should exists and be readable"
)
val fooProps = Utils.loadProperties(fooFile)
val fooProps = fooFile.loadProperties()
fooFile.delete()
fooDir.delete()
@ -94,14 +93,14 @@ class UtilsTest {
@Test
fun testLoadSaveProperties() {
config.properties = propsFile.name
Utils.saveProperties(projectDir, config, version)
saveProperties(projectDir, config, version)
assertEquals(
propsFile.canReadFile(),
propsFile.canRead() && propsFile.isFile,
"properties file should exists and be readable"
)
val props = Utils.loadProperties(propsFile)
val props = propsFile.loadProperties()
assertEquals(props.getProperty(config.majorKey), version.major.toString(), "Major")
assertEquals(props.getProperty(config.minorKey), version.minor.toString(), "Minor")
@ -121,7 +120,7 @@ class UtilsTest {
var locked = File("locked")
assertFailsWith<GradleException> {
Utils.loadProperties(File(locked, propsFile.name))
File(locked, propsFile.name).loadProperties()
}
locked.delete()
@ -132,7 +131,7 @@ class UtilsTest {
if (!locked.canWrite()) {
assertFailsWith<GradleException> {
Utils.saveProperties(locked.parentFile, config, version)
saveProperties(locked.parentFile, config, version)
}
}
locked.delete()
@ -143,9 +142,9 @@ class UtilsTest {
val props = SortedProperties()
props["foo"] = "bar"
assertFailsWith<GradleException> { Utils.loadIntProperty(props, "foo", 1) }
assertFailsWith<GradleException> { loadIntProperty(props, "foo", 1) }
assertEquals(Utils.loadIntProperty(props, "none", 1), 1, "default int value")
assertEquals(loadIntProperty(props, "none", 1), 1, "default int value")
}
@Test
@ -154,7 +153,7 @@ class UtilsTest {
version.buildMetaPrefix = "."
listOf("2.1.0.beta.1", "2.1.1.1", "3.2.1.beta.1.007").forEach {
assertTrue(Utils.parseSemVer(it, version), "parsing semver: $it")
assertTrue(parseSemVer(it, version), "parsing semver: $it")
assertEquals(it, version.semver, it)
}
@ -173,37 +172,34 @@ class UtilsTest {
)
assertTrue(
Utils.isNotSystemProperty(
setOf(
config.majorKey,
config.minorKey,
config.patchKey,
config.preReleaseKey,
config.buildMetaKey
)
),
"none should already exists"
).isNotSystemProperty(), "none should already exists"
)
val props = Utils.loadProperties(propsFile)
val props = propsFile.loadProperties()
sysProps.forEach {
val msg = "${it.first} should match system properties"
System.getProperties().setProperty(it.first, it.second)
if (it.first == config.majorKey || it.first == config.minorKey || it.first == config.patchKey) {
assertEquals(Utils.loadIntProperty(props, it.first, -1), it.second.toInt(), msg)
assertEquals(loadIntProperty(props, it.first, -1), it.second.toInt(), msg)
} else {
assertEquals(Utils.loadProperty(props, it.first, ""), it.second, msg)
assertEquals(loadProperty(props, it.first, ""), it.second, msg)
}
}
Utils.loadVersion(config, version, props)
loadVersion(config, version, props)
assertEquals(version.semver, "2.1.1-beta+007", "version should be identical")
Utils.saveProperties(projectDir, config, version)
saveProperties(projectDir, config, version)
val newPropsFile = File(config.properties)
val newProps = Utils.loadProperties(newPropsFile)
val newProps = newPropsFile.loadProperties()
sysProps.forEach {
assertEquals(newProps.getProperty(it.first), it.second, "new properties should validate")
@ -213,7 +209,7 @@ class UtilsTest {
System.getProperties().setProperty(config.semverKey, "3.2.2")
props["foo"] = "bar"
Utils.loadVersion(config, version, props)
loadVersion(config, version, props)
assertEquals(version.semver, System.getProperty(config.semverKey), "versions should match")
}
@ -228,7 +224,7 @@ class UtilsTest {
"111.11.11-beta",
"1111.111.11-beta+001.12"
).forEach {
assertTrue(Utils.parseSemVer(it, version), "parsing semver: $it")
assertTrue(parseSemVer(it, version), "parsing semver: $it")
assertEquals(it, version.semver, it)
}
}