From 72ef9317f904b53db9254c3fba94c557e6d53a4a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:23:23 +0600 Subject: [PATCH 1/8] start 2.5.1 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18fb0e0..05be479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 2.5.1 + ## 2.5.0 * `Version`: diff --git a/gradle.properties b/gradle.properties index d6b7705..622749f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,5 +36,5 @@ androidx_work_version=2.9.1 ## Common -version=2.5.0 -android_code_version=42 +version=2.5.1 +android_code_version=43 From ca5794726f12615d0d80af312ef3d8be497ebee7 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:27:43 +0600 Subject: [PATCH 2/8] update dependencies --- CHANGELOG.md | 8 ++++++-- gradle.properties | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05be479..babe0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ ## 2.5.1 +* `Version`: + * `Kotlin`: `2.0.21` + * `Coroutines`: `1.9.0` + * `Serialization`: `1.7.3` + ## 2.5.0 * `Version`: * `Kotlin`: `2.0.20` - * `Coroutines`: `1.9.0` - * `Serialization`: `1.7.3` + * `AndroidXWork`: `2.10.0` ## 2.4.0 diff --git a/gradle.properties b/gradle.properties index 622749f..f9510e3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ android.useAndroidX=true android.enableJetifier=false -kotlin_version=2.0.20 +kotlin_version=2.0.21 kotlin_coroutines_version=1.9.0 kotlin_serialization_version=1.7.3 @@ -32,7 +32,7 @@ junit_version=4.12 test_ext_junit_version=1.1.3 espresso_core=3.4.0 -androidx_work_version=2.9.1 +androidx_work_version=2.10.0 ## Common diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 95d3181..2e8f7eb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip From e4b3a5059a354ff1d45c56ac8aeb372b543b55e8 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:54:51 +0600 Subject: [PATCH 3/8] fix of #126 --- CHANGELOG.md | 2 + src/commonMain/kotlin/KrontabConfig.kt | 52 ++++++++++++++++-------- src/commonMain/kotlin/internal/Parser.kt | 4 ++ src/commonTest/kotlin/StringParseTest.kt | 51 +++++++++++++++++++++++ 4 files changed, 91 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index babe0ff..67edc61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ## 2.5.0 +* Add cleaning up of incoming template, which must remove all malformed parts from string template (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126)) +* Add support of insufficient amount of arguments (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126)) * `Version`: * `Kotlin`: `2.0.20` * `AndroidXWork`: `2.10.0` diff --git a/src/commonMain/kotlin/KrontabConfig.kt b/src/commonMain/kotlin/KrontabConfig.kt index d0aa227..62c9062 100644 --- a/src/commonMain/kotlin/KrontabConfig.kt +++ b/src/commonMain/kotlin/KrontabConfig.kt @@ -1,5 +1,6 @@ package dev.inmo.krontab +import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.CronDateTimeScheduler import dev.inmo.krontab.internal.CronDateTimeSchedulerTz import dev.inmo.krontab.internal.createKronScheduler @@ -94,27 +95,37 @@ value class KrontabConfig( var dayOfWeekParsed: Array? = null var yearParsed: Array? = null var millisecondsParsed: Array? = null - val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template.split(" ").also { - listOfNotNull( - it.getOrNull(5), - it.getOrNull(6), - it.getOrNull(7), - it.getOrNull(8) - ).forEach { - val offsetFromString = parseOffset(it) - val dayOfWeekFromString = parseWeekDay(it) - val millisecondsFromString = parseMilliseconds(it) - offsetParsed = offsetParsed ?: offsetFromString - dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString - millisecondsParsed = millisecondsParsed ?: millisecondsFromString - when { - dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach - yearParsed == null -> { - yearParsed = parseYears(it) + val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template + .split(Regex("\\s")) + .filter { it.matches(KrontabConfigPartRegex) } // filter garbage from string + .let { + if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code + it + (it.size until 5).map { "*" } + } else { + it + } + } + .also { + listOfNotNull( + it.getOrNull(5), + it.getOrNull(6), + it.getOrNull(7), + it.getOrNull(8) + ).forEach { + val offsetFromString = parseOffset(it) + val dayOfWeekFromString = parseWeekDay(it) + val millisecondsFromString = parseMilliseconds(it) + offsetParsed = offsetParsed ?: offsetFromString + dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString + millisecondsParsed = millisecondsParsed ?: millisecondsFromString + when { + dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach + yearParsed == null -> { + yearParsed = parseYears(it) + } } } } - } val secondsParsed = parseSeconds(secondsSource) val minutesParsed = parseMinutes(minutesSource) @@ -162,4 +173,9 @@ value class KrontabConfig( ) } } + + companion object { + val spacesRegex = Regex("\\s") + val numberRegex = Regex("\\d+") + } } diff --git a/src/commonMain/kotlin/internal/Parser.kt b/src/commonMain/kotlin/internal/Parser.kt index c02cfa0..54ceb5c 100644 --- a/src/commonMain/kotlin/internal/Parser.kt +++ b/src/commonMain/kotlin/internal/Parser.kt @@ -37,6 +37,10 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange, dataCon return results.map(dataConverter) } +internal val KrontabPartNumberRegexString = "((\\d+\\-\\d+)|([\\d\\*]+(/[\\d\\*]+)?))" +internal val KrontabPartsNumberRegexString = "$KrontabPartNumberRegexString(,$KrontabPartNumberRegexString)*" +internal val KrontabConfigPartRegex = Regex("(($KrontabPartsNumberRegexString+)|(\\d+o)|($KrontabPartsNumberRegexString+w)|($KrontabPartsNumberRegexString+ms))") + internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null } internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null } internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() } diff --git a/src/commonTest/kotlin/StringParseTest.kt b/src/commonTest/kotlin/StringParseTest.kt index 3e76d0a..0e44c94 100644 --- a/src/commonTest/kotlin/StringParseTest.kt +++ b/src/commonTest/kotlin/StringParseTest.kt @@ -29,6 +29,57 @@ class StringParseTest { } } @Test + fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithWrongAmountOfSpaces() { + val kronScheduler = buildSchedule("*/1 * * * * ") + + val flow = kronScheduler.asFlowWithoutDelays() + + runTest { + val mustBeCollected = 10 + var collected = 0 + flow.takeWhile { + collected < mustBeCollected + }.collect { + collected++ + } + assertEquals(mustBeCollected, collected) + } + } + @Test + fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithGarbageInTemplate() { + val kronScheduler = buildSchedule(" sdf */1 * * * oo * ") + + val flow = kronScheduler.asFlowWithoutDelays() + + runTest { + val mustBeCollected = 10 + var collected = 0 + flow.takeWhile { + collected < mustBeCollected + }.collect { + collected++ + } + assertEquals(mustBeCollected, collected) + } + } + @Test + fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithInsufficientArgsInTemplate() { + val kronScheduler = buildSchedule(" sdf */1 ") + + val flow = kronScheduler.asFlowWithoutDelays() + + runTest { + val mustBeCollected = 10 + var collected = 0 + flow.takeWhile { + collected < mustBeCollected + }.collect { + collected++ + } + assertEquals(mustBeCollected, collected) + } + } + @Test fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() { val kronScheduler = buildSchedule("*/1 * * * * 500ms") From e0f9db9c60a3d89f62306a93069559f2650a4872 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:56:10 +0600 Subject: [PATCH 4/8] fix of changelog --- CHANGELOG.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67edc61..54d6185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,19 @@ ## 2.5.1 -* `Version`: - * `Kotlin`: `2.0.21` - * `Coroutines`: `1.9.0` - * `Serialization`: `1.7.3` - -## 2.5.0 - * Add cleaning up of incoming template, which must remove all malformed parts from string template (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126)) * Add support of insufficient amount of arguments (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126)) * `Version`: - * `Kotlin`: `2.0.20` + * `Kotlin`: `2.0.21` * `AndroidXWork`: `2.10.0` +## 2.5.0 + +* `Version`: + * `Kotlin`: `2.0.20` + * `Coroutines`: `1.9.0` + * `Serialization`: `1.7.3` + ## 2.4.0 * `Version`: From aff50f0aecc522ccc8ddd723c257e2e6ba1a2bd3 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:57:32 +0600 Subject: [PATCH 5/8] get back splitting by simple ' ' instead of regex in KrontabConfig --- src/commonMain/kotlin/KrontabConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/KrontabConfig.kt b/src/commonMain/kotlin/KrontabConfig.kt index 62c9062..58bdbe0 100644 --- a/src/commonMain/kotlin/KrontabConfig.kt +++ b/src/commonMain/kotlin/KrontabConfig.kt @@ -96,7 +96,7 @@ value class KrontabConfig( var yearParsed: Array? = null var millisecondsParsed: Array? = null val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template - .split(Regex("\\s")) + .split(" ") .filter { it.matches(KrontabConfigPartRegex) } // filter garbage from string .let { if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code From 6b6b7a6fe0d0981befd09f07307f01b619cc3941 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:58:26 +0600 Subject: [PATCH 6/8] remove redundant regexes --- src/commonMain/kotlin/KrontabConfig.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/commonMain/kotlin/KrontabConfig.kt b/src/commonMain/kotlin/KrontabConfig.kt index 58bdbe0..2a3c145 100644 --- a/src/commonMain/kotlin/KrontabConfig.kt +++ b/src/commonMain/kotlin/KrontabConfig.kt @@ -173,9 +173,4 @@ value class KrontabConfig( ) } } - - companion object { - val spacesRegex = Regex("\\s") - val numberRegex = Regex("\\d+") - } } From d82107b8b878da7e2cf47d19f63acedcf383f0c6 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 12:59:35 +0600 Subject: [PATCH 7/8] remove gitea publishing step from workflows --- .github/workflows/publishing_packages.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/publishing_packages.yml b/.github/workflows/publishing_packages.yml index 5c17818..142729e 100644 --- a/.github/workflows/publishing_packages.yml +++ b/.github/workflows/publishing_packages.yml @@ -14,11 +14,6 @@ jobs: sed -i -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" gradle.properties - name: prebuild run: ./gradlew clean build - - name: Publish to Gitea - continue-on-error: true - run: ./gradlew publishAllPublicationsToGiteaRepository - env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - name: Publish package continue-on-error: true run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository From 5ac1bc26e163f96eab11c815c5aee936cbc13355 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 13:30:20 +0600 Subject: [PATCH 8/8] rewrite check onto fsm --- src/commonMain/kotlin/KrontabConfig.kt | 2 +- src/commonMain/kotlin/internal/Parser.kt | 72 +++++++++++++++++++++++- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/KrontabConfig.kt b/src/commonMain/kotlin/KrontabConfig.kt index 2a3c145..2afcb2f 100644 --- a/src/commonMain/kotlin/KrontabConfig.kt +++ b/src/commonMain/kotlin/KrontabConfig.kt @@ -97,7 +97,7 @@ value class KrontabConfig( var millisecondsParsed: Array? = null val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template .split(" ") - .filter { it.matches(KrontabConfigPartRegex) } // filter garbage from string + .filter { checkIncomingPart(it) } // filter garbage from string .let { if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code it + (it.size until 5).map { "*" } diff --git a/src/commonMain/kotlin/internal/Parser.kt b/src/commonMain/kotlin/internal/Parser.kt index 54ceb5c..caaad9b 100644 --- a/src/commonMain/kotlin/internal/Parser.kt +++ b/src/commonMain/kotlin/internal/Parser.kt @@ -37,9 +37,75 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange, dataCon return results.map(dataConverter) } -internal val KrontabPartNumberRegexString = "((\\d+\\-\\d+)|([\\d\\*]+(/[\\d\\*]+)?))" -internal val KrontabPartsNumberRegexString = "$KrontabPartNumberRegexString(,$KrontabPartNumberRegexString)*" -internal val KrontabConfigPartRegex = Regex("(($KrontabPartsNumberRegexString+)|(\\d+o)|($KrontabPartsNumberRegexString+w)|($KrontabPartsNumberRegexString+ms))") +/** + * FSM for parsing of incoming data. If at the end of parsing it have non-null state and string is not empty, data passed check + * + * 1. + * * \\d -> 1 + * * \\* -> 2 + * * \\- -> 5 + * * , -> 1 + * * m -> 6 + * * o -> 7 + * * w -> 7 + * 2. + * * / -> 3 + * 3. + * * \\d -> 3 + * * \\* -> 4 + * 4. + * * , -> 1 + * 5. + * * \\d -> 5 + * * , -> 1 + * 6. + * * s -> 7 + * 7. Empty, end of parse + */ +private val checkIncomingPartTransitionsMap = listOf( + listOf( + Regex("\\d") to 0, + Regex("\\*") to 1, + Regex("-") to 4, + Regex(",") to 0, + Regex("m") to 5, + Regex("o") to 6, + Regex("w") to 6, + ), + listOf( + Regex("/") to 2, + ), + listOf( + Regex("\\d") to 2, + Regex("\\*") to 3, + ), + listOf( + Regex(",") to 0, + ), + listOf( + Regex("\\d") to 4, + Regex(",") to 0, + ), + listOf( + Regex("s") to 6, // end of ms + ), + listOf(), // empty state, end of parsing +) +internal fun checkIncomingPart(part: String): Boolean { + var i = 0 + var state = checkIncomingPartTransitionsMap[0] + while (i < part.length) { + val char = part[i] + val nextState = state.firstNotNullOfOrNull { + it.second.takeIf { _ -> it.first.matches("$char") } + } + if (nextState == null) return false + state = checkIncomingPartTransitionsMap[nextState] + i++ + } + + return part.isNotEmpty() +} internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null } internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null }