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")