From c9c1a27fa9d3fad7cf32a3b9d184cbed57ea7a33 Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Sun, 24 Mar 2024 17:59:48 +0100 Subject: [PATCH] fix: cron compatibility to unix standard - 5 tokens (#3) * chore: fix cronexp to more standart version * chore: readme * chore: scope * chore: naming * chore: bump junit * chore: bump junit * chore: refactoring and naming --- README.md | 8 +- build.gradle.kts | 1 + gradle.properties | 2 +- gradle/libs.versions.toml | 2 + .../kron/ExpressionElementProvider.kt | 86 ++++++ .../github/yamilmedina/kron/KronExpression.kt | 57 ++++ .../yamilmedina/kron/NaturalKronParser.kt | 172 +++++++++++ .../kron/elementprovider/DayNumber.kt | 84 +++++ .../kron/elementprovider/hour/Base12Hour.kt | 25 ++ .../elementprovider/hour/Base12HourShort.kt | 33 ++ .../kron/elementprovider/hour/Base24Hour.kt | 80 +++++ .../kron/elementprovider/hour/Midnight.kt | 44 +++ .../kron/elementprovider/hour/Noon.kt | 74 +++++ .../elementprovider/recurring/EveryDay.kt | 96 ++++++ .../recurring/EveryDayNumber.kt | 98 ++++++ .../elementprovider/recurring/EveryHour.kt | 88 ++++++ .../elementprovider/recurring/EveryMinute.kt | 88 ++++++ .../elementprovider/recurring/EveryMonth.kt | 98 ++++++ .../elementprovider/recurring/EveryWeek.kt | 81 +++++ .../elementprovider/recurring/EveryYear.kt | 79 +++++ .../naturalkron/ExpressionElementProvider.kt | 46 --- .../yamilmedina/naturalkron/KronExpression.kt | 292 ------------------ .../naturalkron/KronParserException.kt | 3 - .../NaturalKronExpressionParser.kt | 168 ---------- .../naturalkron/elementprovider/DayNumber.kt | 73 ----- .../elementprovider/hour/Base12Hour.kt | 36 --- .../elementprovider/hour/Base12HourShort.kt | 43 --- .../elementprovider/hour/Base24Hour.kt | 77 ----- .../elementprovider/hour/Midnight.kt | 64 ---- .../naturalkron/elementprovider/hour/Noon.kt | 15 - .../elementprovider/recurring/EveryDay.kt | 95 ------ .../recurring/EveryDayNumber.kt | 93 ------ .../recurring/EveryDayNumberStep.kt | 73 ----- .../elementprovider/recurring/EveryHour.kt | 80 ----- .../elementprovider/recurring/EveryMinute.kt | 80 ----- .../elementprovider/recurring/EveryMonth.kt | 100 ------ .../recurring/EveryMonthStep.kt | 73 ----- .../elementprovider/recurring/EveryWeek.kt | 73 ----- .../elementprovider/recurring/EveryYear.kt | 52 ---- .../yamilmedina/kron/NaturalKronParserTest.kt | 71 +++++ .../NaturalKronExpressionParserTest.kt | 57 ---- 41 files changed, 1362 insertions(+), 1598 deletions(-) create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/ExpressionElementProvider.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/KronExpression.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/NaturalKronParser.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/DayNumber.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12Hour.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12HourShort.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base24Hour.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Midnight.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Noon.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDay.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDayNumber.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryHour.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMinute.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMonth.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryWeek.kt create mode 100644 src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryYear.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/ExpressionElementProvider.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/KronExpression.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/KronParserException.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParser.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/DayNumber.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12Hour.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12HourShort.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base24Hour.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Midnight.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Noon.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDay.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumber.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumberStep.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryHour.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMinute.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonth.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonthStep.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryWeek.kt delete mode 100644 src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryYear.kt create mode 100644 src/test/kotlin/io/github/yamilmedina/kron/NaturalKronParserTest.kt delete mode 100644 src/test/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParserTest.kt diff --git a/README.md b/README.md index d18e540..9b15891 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,18 @@ A parser that converts natural (English) language to a cron expression in Kotlin You can add the library to your project using gradle: ```kotlin -implementation("io.github.yamilmedina:natural-kron:0.0.1") +implementation("io.github.yamilmedina:natural-kron:0.1.0") ``` ## Usage ## ```kotlin -import io.github.yamilmedina.naturalkron.NaturalKron +import io.github.yamilmedina.kron.NaturalKronParser val expression = "every day at 9am" -val parsed = NaturalKronExpressionParser().parse(expression) +val parsed = NaturalKronParser().parse(expression) -val expectedKronExpressionEveryDayAt9am = "0 0 9 * * *" +val expectedKronExpressionEveryDayAt9am = "0 9 * * *" assertEquals("*", parsed.dayOfWeek) // --> every day cron expression assertEquals(expectedKronExpressionEveryDayAt9am, parsed.toString()) // --> TRUE ``` diff --git a/build.gradle.kts b/build.gradle.kts index cf35f96..fd4638b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,7 @@ repositories { dependencies { testImplementation(libs.kotlin.test) + testImplementation(libs.junit.params) } tasks.test { diff --git a/gradle.properties b/gradle.properties index 571c7f1..4a13ab4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ kotlin.code.style=official -VERSION_NAME=0.0.3 +VERSION_NAME=0.1.0 SONATYPE_HOST=CENTRAL_PORTAL RELEASE_SIGNING_ENABLED=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 166732e..00bea95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,7 @@ kotlin = "1.9.23" maven-publish = "0.28.0" kover = "0.7.4" +junit = "5.10.2" [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } @@ -10,3 +11,4 @@ kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } [libraries] kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } +junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } diff --git a/src/main/kotlin/io/github/yamilmedina/kron/ExpressionElementProvider.kt b/src/main/kotlin/io/github/yamilmedina/kron/ExpressionElementProvider.kt new file mode 100644 index 0000000..eec9453 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/ExpressionElementProvider.kt @@ -0,0 +1,86 @@ +package io.github.yamilmedina.kron + +internal interface ExpressionElementProvider { + /** + * @param string + * @return Boolean + */ + fun matches(string: String): Boolean + + /** + * @return Boolean + */ + fun canProvideMinute(): Boolean + + /** + * @return String + */ + fun getMinuteElement(): String? + + /** + * @return Boolean + */ + fun isMinuteElementLocked(): Boolean + + /** + * @return Boolean + */ + fun canProvideHour(): Boolean + + /** + * @return String + */ + fun getHourElement(): String? + + /** + * @return Boolean + */ + fun isHourElementLocked(): Boolean + + /** + * @return Boolean + */ + fun canProvideDayNumber(): Boolean + + /** + * @return String + */ + fun getDayNumberElement(): String? + + /** + * @return Boolean + */ + fun isDayNumberElementLocked(): Boolean + + /** + * @return Boolean + */ + fun canProvideMonth(): Boolean + + /** + * @return String + */ + fun getMonthElement(): String? + + /** + * @return Boolean + */ + fun isMonthElementLocked(): Boolean + + /** + * @return Boolean + */ + fun canProvideDayOfWeek(): Boolean + + /** + * @return String + */ + fun getDayOfWeekElement(): String? + + /** + * @return Boolean + */ + fun isDayOfWeekElementLocked(): Boolean +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/KronExpression.kt b/src/main/kotlin/io/github/yamilmedina/kron/KronExpression.kt new file mode 100644 index 0000000..1fa1c79 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/KronExpression.kt @@ -0,0 +1,57 @@ +package io.github.yamilmedina.kron + +class KronExpression( + var minute: String? = null, + var hour: String? = null, + var dayNumber: String? = null, + var month: String? = null, + var dayOfWeek: String? = null +) { + + fun hasMinute(): Boolean = minute != null + + fun setMinute(minute: String): KronExpression { + this.minute = minute + return this + } + + fun hasHour(): Boolean = hour != null + + fun setHour(hour: String): KronExpression { + this.hour = hour + return this + } + + fun hasDayNumber(): Boolean = dayNumber != null + + fun setDayNumber(dayNumber: String): KronExpression { + this.dayNumber = dayNumber + return this + } + + fun hasMonth(): Boolean = month != null + + fun setMonth(month: String): KronExpression { + this.month = month + return this + } + + fun hasDayOfWeek(): Boolean = dayOfWeek != null + + fun setDayOfWeek(dayOfWeek: String): KronExpression { + this.dayOfWeek = dayOfWeek + return this + } + + fun hasNothing(): Boolean = !hasMinute() && !hasHour() && !hasDayNumber() && !hasMonth() && !hasDayOfWeek() + + override fun toString(): String = "%s %s %s %s %s".format( + if (hasMinute()) minute else "0", + if (hasHour()) hour else "0", + if (hasDayNumber()) dayNumber else "*", + if (hasMonth()) month else "*", + if (hasDayOfWeek()) dayOfWeek else "*" + ) +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/NaturalKronParser.kt b/src/main/kotlin/io/github/yamilmedina/kron/NaturalKronParser.kt new file mode 100644 index 0000000..9c6135e --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/NaturalKronParser.kt @@ -0,0 +1,172 @@ +package io.github.yamilmedina.kron + +import io.github.yamilmedina.kron.elementprovider.DayNumber +import io.github.yamilmedina.kron.elementprovider.hour.Base12Hour +import io.github.yamilmedina.kron.elementprovider.hour.Base12HourShort +import io.github.yamilmedina.kron.elementprovider.hour.Base24Hour +import io.github.yamilmedina.kron.elementprovider.hour.Midnight +import io.github.yamilmedina.kron.elementprovider.hour.Noon +import io.github.yamilmedina.kron.elementprovider.recurring.EveryDay +import io.github.yamilmedina.kron.elementprovider.recurring.EveryDayNumber +import io.github.yamilmedina.kron.elementprovider.recurring.EveryHour +import io.github.yamilmedina.kron.elementprovider.recurring.EveryMinute +import io.github.yamilmedina.kron.elementprovider.recurring.EveryMonth +import io.github.yamilmedina.kron.elementprovider.recurring.EveryWeek +import io.github.yamilmedina.kron.elementprovider.recurring.EveryYear +import java.text.ParseException +import java.util.regex.Pattern + +class NaturalKronParser { + + companion object { + const val VALID_PATTERN = + "^(@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly|((?:[1-9]?\\d|\\*)\\s*(?:(?:[\\/\\-][1-9]?\\d)|(?:,[1-9]?\\d)+)?\\s*){5})$" + } + + private val elementProviders: List = listOf( + EveryYear(), + EveryMonth(), + EveryWeek(), + EveryDayNumber(), + EveryDay(), + EveryHour(), + EveryMinute(), + DayNumber(), + Noon(), + Midnight(), + Base12Hour(), + Base12HourShort(), + Base24Hour() + ) + + /** + * Parses a string to a KronExpression + * + * @param string the string to parse + * @throws ParseException if the string is not a valid cron expression + * @return a [KronExpression] instance + */ + fun parse(string: String): KronExpression { + val lowercaseString = string.toLowerCase() + val mappings = mapOf( + "@yearly" to KronExpression("0", "0", "1", "1", "*"), + "@annually" to KronExpression("0", "0", "1", "1", "*"), + "@monthly" to KronExpression("0", "0", "1", "*", "*"), + "@weekly" to KronExpression("0", "0", "*", "*", "0"), + "@midnight" to KronExpression("0", "0", "*", "*", "*"), + "@daily" to KronExpression("0", "0", "*", "*", "*"), + "@hourly" to KronExpression("0", "*", "*", "*", "*") + ) + + if (mappings.containsKey(lowercaseString)) { + return mappings[lowercaseString]!! + } + + val expression = KronExpression() + var isMinuteElementLocked = false + var isHourElementLocked = false + var isDayNumberElementLocked = false + var isMonthElementLocked = false + var isDayOfWeekElementLocked = false + + val shouldUpdateMinute: (KronExpression, ExpressionElementProvider) -> Boolean = { expression, subParser -> + subParser.canProvideMinute() && !isMinuteElementLocked + } + + val shouldUpdateHour: (KronExpression, ExpressionElementProvider) -> Boolean = { expression, subParser -> + subParser.canProvideHour() && !isHourElementLocked + } + + val shouldUpdateDayNumber: (KronExpression, ExpressionElementProvider) -> Boolean = { expression, subParser -> + subParser.canProvideDayNumber() && !isDayNumberElementLocked + } + + val shouldUpdateMonth: (KronExpression, ExpressionElementProvider) -> Boolean = { expression, subParser -> + subParser.canProvideMonth() && !isMonthElementLocked + } + + val shouldUpdateDayOfWeek: (KronExpression, ExpressionElementProvider) -> Boolean = { expression, subParser -> + subParser.canProvideDayOfWeek() && !isDayOfWeekElementLocked + } + + for (elementProvider in elementProviders) { + if (elementProvider.matches(lowercaseString)) { + if (shouldUpdateMinute(expression, elementProvider)) { + expression.minute = elementProvider.getMinuteElement() + } + + if (shouldUpdateHour(expression, elementProvider)) { + expression.hour = elementProvider.getHourElement() + } + + if (shouldUpdateDayNumber(expression, elementProvider)) { + expression.dayNumber = elementProvider.getDayNumberElement() + } + + if (shouldUpdateMonth(expression, elementProvider)) { + expression.month = elementProvider.getMonthElement() + } + + if (shouldUpdateDayOfWeek(expression, elementProvider)) { + expression.dayOfWeek = elementProvider.getDayOfWeekElement() + } + + if (elementProvider.isMinuteElementLocked()) { + isMinuteElementLocked = true + } + + if (elementProvider.isHourElementLocked()) { + isHourElementLocked = true + } + + if (elementProvider.isDayNumberElementLocked()) { + isDayNumberElementLocked = true + } + + if (elementProvider.isMonthElementLocked()) { + isMonthElementLocked = true + } + + if (elementProvider.isDayOfWeekElementLocked()) { + isDayOfWeekElementLocked = true + } + } + } + + val validPattern = Pattern.compile(VALID_PATTERN) + if (expression.hasNothing() || !validPattern.matcher(expression.toString()).matches()) { + throw ParseException("Unable to parse \"$string\", expression is: $expression", Int.MIN_VALUE) + } + + return expression + } + + /** + * Converts a string to a valid cron expression + * + * @param string the string to convert + * @throws ParseException if the string is not a valid cron expression + * @return a valid cron expression in string format + */ + fun fromString(string: String): String { + val parser = NaturalKronParser() + return parser.parse(string).toString() + } + + /** + * Checks if a string is a valid cron expression + */ + fun isValid(string: String): Boolean { + if (string == "@reboot") { + return true + } + return try { + fromString(string) + true + } catch (e: ParseException) { + false + } + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/DayNumber.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/DayNumber.kt new file mode 100644 index 0000000..e0872e7 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/DayNumber.kt @@ -0,0 +1,84 @@ +package io.github.yamilmedina.kron.elementprovider + +import io.github.yamilmedina.kron.ExpressionElementProvider +import java.util.regex.Pattern + +internal class DayNumber : ExpressionElementProvider { + companion object { + private const val PATTERN = "([0-9]?[0-9])(st|nd|rd|th)" + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) + val matcher = regex.matcher(string) + if (matcher.find()) { + segments = (1..matcher.groupCount()).map { matcher.group(it) } + return true + } + return false + } + + override fun canProvideMinute(): Boolean { + return true + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return true + } + + override fun getHourElement(): String { + return 0.toString() + } + + override fun canProvideDayNumber(): Boolean { + return true + } + + override fun getDayNumberElement(): String { + return segments[0] + } + + override fun canProvideMonth(): Boolean { + return segments.isNotEmpty() + } + + override fun getMonthElement(): String { + return "*" + } + + override fun canProvideDayOfWeek(): Boolean { + return true + } + + override fun getDayOfWeekElement(): String { + return "*" + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return true + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12Hour.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12Hour.kt new file mode 100644 index 0000000..b682f37 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12Hour.kt @@ -0,0 +1,25 @@ +package io.github.yamilmedina.kron.elementprovider.hour + + +internal open class Base12Hour : Base24Hour() { + + companion object { + const val PATTERN = "(1[012]|[1-9]):([0-5][0-9])?(?i)\\s?(am|pm)" + } + + override fun matches(string: String): Boolean { + val regex = Regex(PATTERN, RegexOption.IGNORE_CASE) + val matches = regex.find(string) + this.segments = matches?.groupValues?.toList() ?: emptyList() + return this.segments.isNotEmpty() + } + + override fun getHourElement(): String { + println("> 12 Hour segments: $segments") + return when { + segments[3].toLowerCase() == "pm" && segments[1].toInt() < 12 -> (segments[1].toInt() + 12).toString() + segments[3].toLowerCase() == "am" && segments[1].toInt() == 12 -> 0.toString() + else -> segments[1] + } + } +} diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12HourShort.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12HourShort.kt new file mode 100644 index 0000000..8cf5047 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base12HourShort.kt @@ -0,0 +1,33 @@ +package io.github.yamilmedina.kron.elementprovider.hour + +internal open class Base12HourShort : Base12Hour() { + + companion object { + const val PATTERN = "(1[012]|[1-9])\\s?(?i)\\s?(am|pm)" + } + + override fun matches(string: String): Boolean { + val regex = Regex(PATTERN, RegexOption.IGNORE_CASE) + val matches = regex.find(string) + this.segments = matches?.groupValues?.toList() ?: emptyList() + return this.segments.isNotEmpty() + } + + override fun getHourElement(): String { + return when { + segments[2].toLowerCase() == "pm" && segments[1].toInt() < 12 -> (segments[1].toInt() + 12).toString() + segments[2].toLowerCase() == "am" && segments[1].toInt() == 12 -> 0.toString() + else -> segments[1] + } + } + + override fun canProvideMinute(): Boolean { + return segments.size > 1 + } + + override fun getMinuteElement(): String { + return 0.toString() + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base24Hour.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base24Hour.kt new file mode 100644 index 0000000..7bf1cde --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Base24Hour.kt @@ -0,0 +1,80 @@ +package io.github.yamilmedina.kron.elementprovider.hour + +import io.github.yamilmedina.kron.ExpressionElementProvider + +internal open class Base24Hour : ExpressionElementProvider { + + companion object { + const val PATTERN = "(2[0-3]|[01]?[0-9]):([0-5]?[0-9])" + } + + protected var segments: List = listOf() + + override fun matches(string: String): Boolean { + val regex = Regex(PATTERN, RegexOption.IGNORE_CASE) + val matches = regex.find(string) + this.segments = matches?.groupValues?.toList() ?: emptyList() + return this.segments.isNotEmpty() + } + + override fun canProvideMinute(): Boolean { + return segments.size > 2 + } + + override fun getMinuteElement(): String { + return segments[2].toUInt().toString() + } + + override fun canProvideHour(): Boolean { + return segments.size > 1 + } + + override fun getHourElement(): String { + return segments[1].toUInt().toString() + } + + override fun canProvideDayNumber(): Boolean { + return false + } + + override fun getDayNumberElement(): String? { + return null + } + + override fun canProvideMonth(): Boolean { + return false + } + + override fun getMonthElement(): String? { + return null + } + + override fun canProvideDayOfWeek(): Boolean { + return false + } + + override fun getDayOfWeekElement(): String? { + return null + } + + override fun isMinuteElementLocked(): Boolean { + return canProvideMinute() + } + + override fun isHourElementLocked(): Boolean { + return canProvideHour() + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Midnight.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Midnight.kt new file mode 100644 index 0000000..495d0b1 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Midnight.kt @@ -0,0 +1,44 @@ +package io.github.yamilmedina.kron.elementprovider.hour + +import io.github.yamilmedina.kron.ExpressionElementProvider + +internal class Midnight : ExpressionElementProvider { + private var match: Boolean = false + + override fun matches(string: String): Boolean { + match = string.contains("midnight") + return match + } + + override fun canProvideMinute(): Boolean = match + + override fun getMinuteElement(): String = 0.toString() + + override fun canProvideHour(): Boolean = match + + override fun getHourElement(): String = 0.toString() + + override fun canProvideDayNumber(): Boolean = false + + override fun getDayNumberElement(): String? = null + + override fun canProvideMonth(): Boolean = false + + override fun getMonthElement(): String? = null + + override fun canProvideDayOfWeek(): Boolean = false + + override fun getDayOfWeekElement(): String? = null + + override fun isMinuteElementLocked(): Boolean = canProvideMinute() + + override fun isHourElementLocked(): Boolean = canProvideHour() + + override fun isDayNumberElementLocked(): Boolean = false + + override fun isMonthElementLocked(): Boolean = false + + override fun isDayOfWeekElementLocked(): Boolean = false +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Noon.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Noon.kt new file mode 100644 index 0000000..26c3f85 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/hour/Noon.kt @@ -0,0 +1,74 @@ +package io.github.yamilmedina.kron.elementprovider.hour + +import io.github.yamilmedina.kron.ExpressionElementProvider + +internal class Noon : ExpressionElementProvider { + private var match: Boolean = false + + override fun matches(string: String): Boolean { + match = string.contains("noon") + return match + } + + override fun canProvideMinute(): Boolean { + return match + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return match + } + + override fun getHourElement(): String { + return 12.toString() + } + + override fun canProvideDayNumber(): Boolean { + return false + } + + override fun getDayNumberElement(): String? { + return null + } + + override fun canProvideMonth(): Boolean { + return false + } + + override fun getMonthElement(): String? { + return null + } + + override fun canProvideDayOfWeek(): Boolean { + return false + } + + override fun getDayOfWeekElement(): String? { + return null + } + + override fun isMinuteElementLocked(): Boolean { + return canProvideMinute() + } + + override fun isHourElementLocked(): Boolean { + return canProvideHour() + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDay.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDay.kt new file mode 100644 index 0000000..3ff4baa --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDay.kt @@ -0,0 +1,96 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + +import io.github.yamilmedina.kron.ExpressionElementProvider +import java.util.regex.Pattern + +internal class EveryDay : ExpressionElementProvider { + companion object { + private const val PATTERN = + "(daily|(every|each|on)\\s(day|monday|tuesday|wednesday|thursday|friday|saturday|sunday)(?s))" + private val DAY_MAP = mapOf( + "sunday" to 0, + "monday" to 1, + "tuesday" to 2, + "wednesday" to 3, + "thursday" to 4, + "friday" to 5, + "saturday" to 6 + ) + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) + val matcher = regex.matcher(string) + if (matcher.find()) { + segments = (0..matcher.groupCount()).map { matcher.group(it) } + return true + } + return false + } + + override fun canProvideMinute(): Boolean { + return true + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return true + } + + override fun getHourElement(): String { + return 0.toString() + } + + override fun canProvideDayNumber(): Boolean { + return true + } + + override fun getDayNumberElement(): String { + return "*" + } + + override fun canProvideMonth(): Boolean { + return true + } + + override fun getMonthElement(): String { + return "*" + } + + override fun canProvideDayOfWeek(): Boolean { + return segments.isNotEmpty() + } + + override fun getDayOfWeekElement(): String? { + return if (segments[0] == "daily" || (segments.size >= 4 && segments[3] == "day")) { + null + } else { + DAY_MAP[segments.getOrNull(3)]?.toString() + } + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return true + } +} diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDayNumber.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDayNumber.kt new file mode 100644 index 0000000..c9627c4 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryDayNumber.kt @@ -0,0 +1,98 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + +import io.github.yamilmedina.kron.ExpressionElementProvider +import java.util.regex.Pattern + +internal class EveryDayNumber : ExpressionElementProvider { + companion object { + private const val PATTERN = + "(every|each)\\s([0-9]?[0-9])(st|nd|rd|th)\\sof\\s(month|january|february|march|april|may|june|july|august|september|october|november|december)" + private val MONTH_MAP = mapOf( + "january" to 1, + "february" to 2, + "march" to 3, + "april" to 4, + "may" to 5, + "june" to 6, + "july" to 7, + "august" to 8, + "september" to 9, + "october" to 10, + "november" to 11, + "december" to 12 + ) + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) + val matcher = pattern.matcher(string) + if (matcher.find()) { + segments = (0..matcher.groupCount()).map { matcher.group(it) } + return true + } + return false + } + + override fun canProvideMinute(): Boolean { + return true + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return true + } + + override fun getHourElement(): String { + return 0.toString() + } + + override fun canProvideDayNumber(): Boolean { + return true + } + + override fun getDayNumberElement(): String { + return segments[2] + } + + override fun canProvideMonth(): Boolean { + return segments.size > 4 + } + + override fun getMonthElement(): String? { + return MONTH_MAP[segments.getOrNull(4)]?.toString() + } + + override fun canProvideDayOfWeek(): Boolean { + return true + } + + override fun getDayOfWeekElement(): String { + return "*" + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return canProvideMonth() + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryHour.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryHour.kt new file mode 100644 index 0000000..c49fd9e --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryHour.kt @@ -0,0 +1,88 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + +import io.github.yamilmedina.kron.ExpressionElementProvider +import java.util.regex.Pattern + +internal class EveryHour : ExpressionElementProvider { + companion object { + private const val PATTERN = "(hourly|(every|each)?\\s?([0-9]+)?\\shour)" + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) + val matcher = regex.matcher(string) + if (matcher.find()) { + segments = (0..matcher.groupCount()).map { matcher.group(it) } + return true + } + return false + } + + override fun canProvideMinute(): Boolean { + return false + } + + override fun getMinuteElement(): String? { + return null + } + + override fun canProvideHour(): Boolean { + return segments.isNotEmpty() + } + + override fun getHourElement(): String? { + return if (segments.isNotEmpty()) { + segments[3]?.let { "*/$it" } ?: "*" + } else { + null + } + } + + override fun canProvideDayNumber(): Boolean { + return false + } + + override fun getDayNumberElement(): String? { + return null + } + + override fun canProvideMonth(): Boolean { + return false + } + + override fun getMonthElement(): String? { + return null + } + + override fun canProvideDayOfWeek(): Boolean { + return false + } + + override fun getDayOfWeekElement(): String? { + return null + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return true + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMinute.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMinute.kt new file mode 100644 index 0000000..ed69de0 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMinute.kt @@ -0,0 +1,88 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + +import io.github.yamilmedina.kron.ExpressionElementProvider +import java.util.regex.Pattern + +internal class EveryMinute : ExpressionElementProvider { + companion object { + private const val PATTERN = "((every|each)?\\s?([0-9]+)?\\s?minute)" + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) + val matcher = regex.matcher(string) + if (matcher.find()) { + segments = (0..matcher.groupCount()).map { matcher.group(it) } + return true + } + return false + } + + override fun canProvideMinute(): Boolean { + return segments.isNotEmpty() + } + + override fun getMinuteElement(): String? { + return if (segments.isNotEmpty()) { + segments[3]?.let { "*/$it" } ?: "*" + } else { + null + } + } + + override fun canProvideHour(): Boolean { + return canProvideMinute() + } + + override fun getHourElement(): String? { + return "*" + } + + override fun canProvideDayNumber(): Boolean { + return false + } + + override fun getDayNumberElement(): String? { + return null + } + + override fun canProvideMonth(): Boolean { + return false + } + + override fun getMonthElement(): String? { + return null + } + + override fun canProvideDayOfWeek(): Boolean { + return false + } + + override fun getDayOfWeekElement(): String? { + return null + } + + override fun isMinuteElementLocked(): Boolean { + return true + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMonth.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMonth.kt new file mode 100644 index 0000000..3e65321 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryMonth.kt @@ -0,0 +1,98 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + +import io.github.yamilmedina.kron.ExpressionElementProvider + +internal class EveryMonth : ExpressionElementProvider { + + companion object { + private const val PATTERN = + "(monthly|(every|each)\\s(month|january|february|march|april|may|june|july|august|september|october|november|december))" + private val MONTH_MAP = mapOf( + "january" to 1, + "february" to 2, + "march" to 3, + "april" to 4, + "may" to 5, + "june" to 6, + "july" to 7, + "august" to 8, + "september" to 9, + "october" to 10, + "november" to 11, + "december" to 12 + ) + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Regex(PATTERN, RegexOption.IGNORE_CASE) + segments = regex.find(string)?.groupValues?.drop(1) ?: emptyList() + return segments.isNotEmpty() + } + + override fun canProvideMinute(): Boolean { + return true + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return true + } + + override fun getHourElement(): String { + return 0.toString() + } + + override fun canProvideDayNumber(): Boolean { + return true + } + + override fun getDayNumberElement(): String { + return "1" + } + + override fun canProvideMonth(): Boolean { + return segments.isNotEmpty() + } + + override fun getMonthElement(): String? { + return if (segments[0] == "monthly" || (segments.size == 4 && segments[3] == "month")) { + "*" + } else { + MONTH_MAP[segments.getOrNull(2)]?.toString() + } + } + + override fun canProvideDayOfWeek(): Boolean { + return true + } + + override fun getDayOfWeekElement(): String { + return "*" + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryWeek.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryWeek.kt new file mode 100644 index 0000000..074c944 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryWeek.kt @@ -0,0 +1,81 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + + +import io.github.yamilmedina.kron.ExpressionElementProvider + +internal class EveryWeek : ExpressionElementProvider { + + companion object { + private const val PATTERN = "(weekly|(every|each)\\sweek)" + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Regex(PATTERN, RegexOption.IGNORE_CASE) + segments = regex.find(string)?.groupValues?.drop(1) ?: emptyList() + return segments.isNotEmpty() + } + + override fun canProvideMinute(): Boolean { + return true + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return true + } + + override fun getHourElement(): String { + return 0.toString() + } + + override fun canProvideDayNumber(): Boolean { + return true + } + + override fun getDayNumberElement(): String { + return "*" + } + + override fun canProvideMonth(): Boolean { + return true + } + + override fun getMonthElement(): String { + return "*" + } + + override fun canProvideDayOfWeek(): Boolean { + return segments.isNotEmpty() + } + + override fun getDayOfWeekElement(): String { + return "0" + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + + diff --git a/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryYear.kt b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryYear.kt new file mode 100644 index 0000000..9c40b99 --- /dev/null +++ b/src/main/kotlin/io/github/yamilmedina/kron/elementprovider/recurring/EveryYear.kt @@ -0,0 +1,79 @@ +package io.github.yamilmedina.kron.elementprovider.recurring + +import io.github.yamilmedina.kron.ExpressionElementProvider + +internal class EveryYear : ExpressionElementProvider { + + companion object { + private const val PATTERN = "(yearly|annually|(every|each) ?([0-9]+)?year)" + } + + private var segments: List = emptyList() + + override fun matches(string: String): Boolean { + val regex = Regex(PATTERN, RegexOption.IGNORE_CASE) + segments = regex.find(string)?.groupValues ?: emptyList() + return segments.isNotEmpty() + } + + override fun canProvideMinute(): Boolean { + return true + } + + override fun getMinuteElement(): String { + return 0.toString() + } + + override fun canProvideHour(): Boolean { + return true + } + + override fun getHourElement(): String { + return 0.toString() + } + + override fun canProvideDayNumber(): Boolean { + return true + } + + override fun getDayNumberElement(): String { + return 1.toString() + } + + override fun canProvideMonth(): Boolean { + return true + } + + override fun getMonthElement(): String { + return 1.toString() + } + + override fun canProvideDayOfWeek(): Boolean { + return true + } + + override fun getDayOfWeekElement(): String { + return "*" + } + + override fun isMinuteElementLocked(): Boolean { + return false + } + + override fun isHourElementLocked(): Boolean { + return false + } + + override fun isDayNumberElementLocked(): Boolean { + return false + } + + override fun isMonthElementLocked(): Boolean { + return false + } + + override fun isDayOfWeekElementLocked(): Boolean { + return false + } +} + diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/ExpressionElementProvider.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/ExpressionElementProvider.kt deleted file mode 100644 index bee448d..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/ExpressionElementProvider.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.yamilmedina.naturalkron - -internal interface ExpressionElementProvider { - - fun matches(value: String): Boolean - - fun canProvideSecond(): Boolean { - return false - } - - val secondElement: String? - get() = null - - fun canProvideMinute(): Boolean - - val minuteElement: String? - - fun canProvideHour(): Boolean - - val hourElement: String? - - fun canProvideDayNumber(): Boolean - - val dayNumberElement: String? - - fun canProvideMonth(): Boolean - - val monthElement: String? - - fun canProvideDayOfWeek(): Boolean - - val dayOfWeekElement: String? - - val isSecondElementLocked: Boolean - get() = false - - val isMinuteElementLocked: Boolean - - val isHourElementLocked: Boolean - - val isDayNumberElementLocked: Boolean - - val isMonthElementLocked: Boolean - - val isDayOfWeekElementLocked: Boolean -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/KronExpression.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/KronExpression.kt deleted file mode 100644 index 4146a1b..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/KronExpression.kt +++ /dev/null @@ -1,292 +0,0 @@ -package io.github.yamilmedina.naturalkron - -import java.text.SimpleDateFormat -import java.util.* -import java.util.regex.Pattern - -class KronExpression { - var second: String? = null - var minute: String? = null - var hour: String? = null - var dayNumber: String? = null - var month: String? = null - var dayOfWeek: String? = null - - internal constructor() - - constructor( - second: String?, - minute: String?, - hour: String?, - dayNumber: String?, - month: String?, - dayOfWeek: String? - ) : super() { - this.second = second - this.minute = minute - this.hour = hour - this.dayNumber = dayNumber - this.month = month - this.dayOfWeek = dayOfWeek - } - - fun hasSecond(): Boolean { - return second != null - } - - fun setSecond(second: String?): KronExpression { - this.second = second - return this - } - - fun setSecond(second: Int): KronExpression { - return setSecond(second.toString()) - } - - fun hasMinute(): Boolean { - return minute != null - } - - fun setMinute(minute: String?): KronExpression { - this.minute = minute - return this - } - - fun setMinute(minute: Int): KronExpression { - return setMinute(minute.toString()) - } - - fun hasHour(): Boolean { - return hour != null - } - - fun setHour(hour: String?): KronExpression { - this.hour = hour - return this - } - - fun setHour(minute: Int): KronExpression { - return setHour(minute.toString()) - } - - fun hasDayNumber(): Boolean { - return dayNumber != null - } - - fun setDayNumber(dayNumber: String?): KronExpression { - this.dayNumber = dayNumber - return this - } - - fun setDayNumber(minute: Int): KronExpression { - return setDayNumber(minute.toString()) - } - - fun hasMonth(): Boolean { - return month != null - } - - fun setMonth(month: String?): KronExpression { - this.month = month - return this - } - - fun setMonth(minute: Int): KronExpression { - return setMonth(minute.toString()) - } - - fun hasDayOfWeek(): Boolean { - return dayOfWeek != null - } - - fun setDayOfWeek(dayOfWeek: String?): KronExpression { - this.dayOfWeek = dayOfWeek - return this - } - - fun setDayOfWeek(minute: Int): KronExpression { - return setDayOfWeek(minute.toString()) - } - - fun hasNothing(): Boolean { - return !hasMinute() && !hasHour() && !hasDayNumber() && !hasMonth() && !hasDayOfWeek() - } - - fun toNaturalLanguage(): String? { - if (!hasNothing()) { - // check if it's one of our static expressions - val expression = toString() - if (expression == "0 0 0 1 1 *") return "yearly" - if (expression == "0 0 0 1 * *") return "monthly" - if (expression == "0 0 0 * * 0") return "weekly" - if (expression == "0 0 12 * * ?" || expression == "0 0 12 * * *") return "midday" - if (expression == "* * * * * ?" || expression == "* * * * * *") return "every second" - if (expression == "0 * * * * ?" || expression == "0 * * * * *") return "every minute" - if (expression == "0 0 * * * ?" || expression == "0 0 * * * *") return "hourly" - if (expression == "0 0 0 * * *") return "daily" - - val b = StringBuilder() - b.append("every ") - - val timeSetPattern = Pattern.compile("^([1-9]|[0-5][0-9])$") - val timeStepSetPattern = Pattern.compile("^\\*\\/([1-9]|[0-5][0-9])$") - - // minutes - if (timeSetPattern.matcher(minute).matches()) { - if (hour == "*") { - b.append(minute).append(" past the hour") - } - // let hour capture the minute if set - } - - if (timeStepSetPattern.matcher(minute).matches()) { - if (hour == "*") { - b.append(minute!!.replace("[^0-9]".toRegex(), "")).append(" minutes") - } - } - - // hours - if (timeSetPattern.matcher(hour).matches()) { - val h = hour!!.toInt() - - if (minute == "*") { - b.append("minute past ") - .append(if (h >= 12) h - 12 else h) - .append(if (h >= 12) "pm" else "am") - } else if (timeStepSetPattern.matcher(minute).matches()) { - val m = minute!!.replace("[^0-9]".toRegex(), "").toInt() - b.append(m) - .append(" minutes past ") - .append(if (h >= 12) h - 12 else h) - .append(if (h >= 12) "pm" else "am") - } else if (timeSetPattern.matcher(minute).matches()) { - val m = minute!!.replace("[^0-9]".toRegex(), "").toInt() - if (dayNumber == "*") { - b.append("day at ") - } - b.append(if (h >= 12) h - 12 else h) - .append(":") - .append(if (m < 10) "0$m" else m) - .append(if (h >= 12) "pm" else "am") - } else if (minute == "0") { - if (dayNumber == "*") { - b.append("day at ") - } - b.append(if (h >= 12) h - 12 else h) - .append(if (h >= 12) "pm" else "am") - } - } - - if (timeStepSetPattern.matcher(hour).matches()) { - b.append(hour!!.replace("[^0-9]".toRegex(), "")).append(" hours") - } - - // day of month - if (dayNumber != "*") { - val dayOfMonthSetPattern = Pattern.compile("^([1-9]|[1-2][0-9]|3[0-1])$") - if (dayOfMonthSetPattern.matcher(dayNumber).matches()) { - b.append( - String.format( - "%s%s", dayNumber, KronExpression.Companion.getDayNumberSuffix( - dayNumber!!.toInt() - ) - ) - ) - } - } - - // month - if (month != "*") { - val monthSetPattern = Pattern.compile("^([1-9]|1[0-2])$") - if (monthSetPattern.matcher(month).matches()) { - val c = Calendar.getInstance() - c[Calendar.MONTH] = month!!.toInt() - 1 - val sdf = SimpleDateFormat("MMMM") - if (dayNumber != "*") { - b.append(" of ") - } - b.append(sdf.format(c.time)) - } - } - - // day of week - val daySetPattern = Pattern.compile("[0-6]") - if (daySetPattern.matcher(dayOfWeek).matches()) { - val c = Calendar.getInstance() - c[Calendar.DAY_OF_WEEK] = dayOfWeek!!.toInt() + 1 - val sdf = SimpleDateFormat("EEEE") - - if (b.toString().endsWith("minutes")) { - b.append(" on ") - .append(sdf.format(c.time)) - .append("s") - } else { - b.append(sdf.format(c.time)) - } - } - - return b.toString() - } - - return null - } - - override fun toString(): String { - return String.format( - "%s %s %s %s %s %s", - if (hasSecond()) second else 0, - if (hasMinute()) minute else 0, - if (hasHour()) hour else 0, - if (hasDayNumber()) dayNumber else '*', - if (hasMonth()) month else '*', - if (hasDayOfWeek()) dayOfWeek else '*' - ) - } - - companion object { - /** - * Returns a CronExpression for the specified cron expression string. - * - * @param expression A cron string - * @return A CronExpression - */ - fun fromExpression(expression: String?): KronExpression? { - if (expression != null) { - val parts = expression.split("\\s".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (parts.size != 6) { - throw KronParserException("Unexpected expression, expected 5 parts, e.g. * * * * * *.") - } - - val kronExpression = KronExpression() - kronExpression.setSecond(parts[0]) - kronExpression.setMinute(parts[1]) - kronExpression.setHour(parts[2]) - kronExpression.setDayNumber(parts[3]) - kronExpression.setMonth(parts[4]) - kronExpression.setDayOfWeek(parts[5]) - - return kronExpression - } - - return null - } - - /** - * Return a valid suffix for a positional number - * - * @param day - * @return - */ - private fun getDayNumberSuffix(day: Int): String { - if (day in 11..13) { - return "th" - } - return when (day % 10) { - 1 -> "st" - 2 -> "nd" - 3 -> "rd" - else -> "th" - } - } - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/KronParserException.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/KronParserException.kt deleted file mode 100644 index 21f7585..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/KronParserException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.yamilmedina.naturalkron - -data class KronParserException(override val message: String) : IllegalArgumentException(message) \ No newline at end of file diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParser.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParser.kt deleted file mode 100644 index c281718..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParser.kt +++ /dev/null @@ -1,168 +0,0 @@ -package io.github.yamilmedina.naturalkron - -import io.github.yamilmedina.naturalkron.elementprovider.DayNumber -import io.github.yamilmedina.naturalkron.elementprovider.hour.Base12Hour -import io.github.yamilmedina.naturalkron.elementprovider.hour.Base12HourShort -import io.github.yamilmedina.naturalkron.elementprovider.hour.Base24Hour -import io.github.yamilmedina.naturalkron.elementprovider.hour.Midnight -import io.github.yamilmedina.naturalkron.elementprovider.hour.Noon -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryDay -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryDayNumberStep -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryHour -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryMinute -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryMonth -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryMonthStep -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryWeek -import io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryYear -import java.util.* -import java.util.regex.Pattern - -class NaturalKronExpressionParser { - private val mappings: MutableMap by lazy { - mutableMapOf().also { - it["yearly"] = KronExpression("0", "0", "0", "1", "1", "*") - it["annually"] = KronExpression("0", "0", "0", "1", "1", "*") - it["monthly"] = KronExpression("0", "0", "0", "1", "*", "*") - it["weekly"] = KronExpression("0", "0", "0", "*", "*", "0") - it["midnight"] = KronExpression("0", "0", "0", "*", "*", "*") - it["daily"] = KronExpression("0", "0", "0", "*", "*", "*") - it["hourly"] = KronExpression("0", "0", "*", "*", "*", "*") - it["midday"] = KronExpression("0", "0", "12", "*", "*", "*") - } - } - private val elementProviders: List = listOf( - EveryYear(), - EveryMonth(), - EveryMonthStep(), - EveryWeek(), - io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryDayNumber(), - EveryDayNumberStep(), - EveryDay(), - EveryHour(), - EveryMinute(), - DayNumber(), - Noon(), - Midnight(), - Base12Hour(), - Base12HourShort(), - Base24Hour(), - ) - - /** - * Parses a natural language expression into a KronExpression - * - * @param input the natural language expression - * @return the KronExpression - * @throws KronParserException if the input cannot be parsed - */ - fun parse(input: String): KronExpression { - val rawExpression = input.lowercase(Locale.getDefault()) - if (mappings[rawExpression] != null) { - // return a 'cached' value of common expressions - return mappings[rawExpression]!! - } - - val kronExpression = KronExpression() - var isSecondElementLocked = false - var isMinuteElementLocked = false - var isHourElementLocked = false - var isDayNumberElementLocked = false - var isMonthElementLocked = false - var isDayOfWeekElementLocked = false - - elementProviders.forEach { elementProvider -> - if (elementProvider.matches(input)) { - if (shouldUpdateSecond(elementProvider, isSecondElementLocked)) { - kronExpression.setSecond(elementProvider.secondElement) - } - - if (shouldUpdateMinute(elementProvider, isMinuteElementLocked)) { - kronExpression.setMinute(elementProvider.minuteElement) - } - - if (shouldUpdateHour(elementProvider, isHourElementLocked)) { - kronExpression.setHour(elementProvider.hourElement) - } - - if (shouldUpdateDayNumber(elementProvider, isDayNumberElementLocked)) { - kronExpression.setDayNumber(elementProvider.dayNumberElement) - } - - if (shouldUpdateMonth(elementProvider, isMonthElementLocked)) { - kronExpression.setMonth(elementProvider.monthElement) - } - - if (shouldUpdateDayOfWeek(elementProvider, isDayOfWeekElementLocked)) { - kronExpression.setDayOfWeek(elementProvider.dayOfWeekElement) - } - - if (elementProvider.isSecondElementLocked) { - isSecondElementLocked = true - } - - if (elementProvider.isMinuteElementLocked) { - isMinuteElementLocked = true - } - - if (elementProvider.isHourElementLocked) { - isHourElementLocked = true - } - - if (elementProvider.isDayNumberElementLocked) { - isDayNumberElementLocked = true - } - - if (elementProvider.isMonthElementLocked) { - isMonthElementLocked = true - } - - if (elementProvider.isDayOfWeekElementLocked) { - isDayOfWeekElementLocked = true - } - } - } - - val isValidKronExpression = pattern.matcher(kronExpression.toString()) - if (kronExpression.hasNothing() || !isValidKronExpression.matches()) { - throw KronParserException(String.format("Unable to parse \"%s\"", input)) - } - - return kronExpression - } - - internal companion object { - private const val VALID_PATTERN: String = - "^(((?:[1-9]?\\d|\\*)\\s*(?:(?:[\\/-][1-9]?\\d)|(?:,[1-9]?\\d)+)?\\s*){6})$" - private val pattern: Pattern = Pattern.compile(VALID_PATTERN) - - private fun shouldUpdateSecond(subParser: ExpressionElementProvider, isSecondElementLocked: Boolean): Boolean { - return subParser.canProvideSecond() && !isSecondElementLocked - } - - private fun shouldUpdateMinute(subParser: ExpressionElementProvider, isMinuteElementLocked: Boolean): Boolean { - return subParser.canProvideMinute() && !isMinuteElementLocked - } - - private fun shouldUpdateHour(subParser: ExpressionElementProvider, isHourElementLocked: Boolean): Boolean { - return subParser.canProvideHour() && !isHourElementLocked - } - - private fun shouldUpdateDayNumber( - subParser: ExpressionElementProvider, - isDayNumberElementLocked: Boolean - ): Boolean { - return subParser.canProvideDayNumber() && !isDayNumberElementLocked - } - - private fun shouldUpdateMonth(subParser: ExpressionElementProvider, isMonthElementLocked: Boolean): Boolean { - return subParser.canProvideMonth() && !isMonthElementLocked - } - - private fun shouldUpdateDayOfWeek( - subParser: ExpressionElementProvider, - isDayOfWeekElementLocked: Boolean - ): Boolean { - return subParser.canProvideDayOfWeek() && !isDayOfWeekElementLocked - } - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/DayNumber.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/DayNumber.kt deleted file mode 100644 index bda299b..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/DayNumber.kt +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class DayNumber : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override val isMinuteElementLocked: Boolean - get() = false - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override val isHourElementLocked: Boolean - get() = false - - override fun canProvideDayNumber(): Boolean { - return true - } - - override val dayNumberElement: String? - get() = if (segments.size >= 1) segments[1] else null - - override val isDayNumberElementLocked: Boolean - get() = true - - override fun canProvideMonth(): Boolean { - return segments.size > 0 - } - - override val monthElement: String - get() = "*" - - override val isMonthElementLocked: Boolean - get() = false - - override fun canProvideDayOfWeek(): Boolean { - return true - } - - override val dayOfWeekElement: String - get() = "*" - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "([0-9]?[0-9])(st|nd|rd|th)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12Hour.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12Hour.kt deleted file mode 100644 index 74cffa3..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12Hour.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.hour - -import java.util.* -import java.util.regex.Pattern - -internal open class Base12Hour : Base24Hour() { - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - if (m.group(i) != null) { - segments.add(m.group(i)) - } - } - } - return segments.size > 0 - } - - override val hourElement: String? - get() { - if (segments.size > 3) { - if (segments[3].lowercase(Locale.getDefault()) == "pm" && segments[1].toInt() < 12) { - return (segments[1].toInt() + 12).toString() - } else if (segments[3].lowercase(Locale.getDefault()) == "am" && segments[1].toInt() == 12) { - return "0" - } - } - return if (segments.size > 1) segments[1] else null - } - - companion object { - private const val PATTERN = "(1[012]|[1-9]):([0-5][0-9])?(?i)\\s?(am|pm)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12HourShort.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12HourShort.kt deleted file mode 100644 index 0643e46..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base12HourShort.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.hour - -import java.util.* -import java.util.regex.Pattern - -internal class Base12HourShort : Base12Hour() { - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - if (m.group(i) != null) { - segments.add(m.group(i)) - } - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return segments.size > 1 - } - - override val minuteElement: String - get() = "0" - - override val hourElement: String? - get() { - if (segments.size > 2) { - if (segments[2].lowercase(Locale.getDefault()) == "pm" && segments[1].toInt() < 12) { - return (segments[1].toInt() + 12).toString() - } else if (segments[2].lowercase(Locale.getDefault()) == "am" && segments[1].toInt() == 12) { - return "0" - } - } - return if (segments.size > 1) segments[1] else null - } - - companion object { - private const val PATTERN = "(1[012]|[1-9])\\s?(?i)\\s?(am|pm)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base24Hour.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base24Hour.kt deleted file mode 100644 index 8af6118..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Base24Hour.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.hour - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal open class Base24Hour : ExpressionElementProvider { - - @JvmField - protected var segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - if (m.group(i) != null) { - segments.add(m.group(i)) - } - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return segments.size > 2 - } - - override val minuteElement: String? - get() = if (segments.size > 2) segments[2].toInt().toString() else null - - override fun canProvideHour(): Boolean { - return segments.size > 1 - } - - override val hourElement: String? - get() = if (segments.size > 1) segments[1].toInt().toString() else null - - override fun canProvideDayNumber(): Boolean { - return false - } - - override val dayNumberElement: String? - get() = null - - override fun canProvideMonth(): Boolean { - return false - } - - override val monthElement: String? - get() = null - - override fun canProvideDayOfWeek(): Boolean { - return false - } - - override val dayOfWeekElement: String? - get() = null - - override val isMinuteElementLocked: Boolean - get() = canProvideMinute() - - override val isHourElementLocked: Boolean - get() = canProvideHour() - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "(2[0-3]|[01]?[0-9]):([0-5]?[0-9])" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Midnight.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Midnight.kt deleted file mode 100644 index 18d8149..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Midnight.kt +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.hour - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.* - - -internal open class Midnight : ExpressionElementProvider { - protected var match: Boolean = false - - override fun matches(value: String): Boolean { - match = value != null && value.lowercase(Locale.getDefault()).indexOf("midnight") >= 0 - return match - } - - override fun canProvideMinute(): Boolean { - return match - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return match - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return false - } - - override val dayNumberElement: String? - get() = null - - override fun canProvideMonth(): Boolean { - return false - } - - override val monthElement: String? - get() = null - - override fun canProvideDayOfWeek(): Boolean { - return false - } - - override val dayOfWeekElement: String? - get() = null - - override val isMinuteElementLocked: Boolean - get() = canProvideMinute() - - override val isHourElementLocked: Boolean - get() = canProvideHour() - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Noon.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Noon.kt deleted file mode 100644 index 9d1af51..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/hour/Noon.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.hour - -import java.util.* - -internal class Noon : Midnight() { - override fun matches(value: String): Boolean { - match = value != null && - (value.lowercase(Locale.getDefault()).indexOf("noon") >= 0 || value.lowercase(Locale.getDefault()) - .indexOf("midday") >= 0) - return match - } - - override val hourElement: String - get() = "12" -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDay.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDay.kt deleted file mode 100644 index adad6f2..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDay.kt +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.* -import java.util.regex.Pattern - -internal class EveryDay : ExpressionElementProvider { - - private val segments: MutableList = mutableListOf() - private val dayMap: MutableMap by lazy { - mutableMapOf( - "sunday" to "0", - "monday" to "1", - "tuesday" to "2", - "wednesday" to "3", - "thursday" to "4", - "friday" to "5", - "saturday" to "6" - ) - } - - override fun matches(value: String): Boolean { - val m = dayOfWeekPattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return true - } - - override val dayNumberElement: String - get() = "*" - - override fun canProvideMonth(): Boolean { - return true - } - - override val monthElement: String - get() = "*" - - override fun canProvideDayOfWeek(): Boolean { - return segments.size > 0 - } - - override val dayOfWeekElement: String? - get() { - if ((segments.size > 0 && segments[0] == "daily") || (segments.size > 3 && segments[3] == "day")) { - return "*" - } - return if (segments.size > 3 && dayMap[segments[3].lowercase(Locale.getDefault())] != null) dayMap[segments[3].lowercase( - Locale.getDefault() - )] - else null - } - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = true - - companion object { - private const val DAY_OF_WEEK_PATTERN = - "(daily|(every|each|on)\\s(day|monday|tuesday|wednesday|thursday|friday|saturday|sunday)(?s))" - private val dayOfWeekPattern: Pattern = Pattern.compile(DAY_OF_WEEK_PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumber.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumber.kt deleted file mode 100644 index ccc4b14..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumber.kt +++ /dev/null @@ -1,93 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.* -import java.util.regex.Pattern - -internal class EveryDayNumber : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - private val monthMap: MutableMap by lazy { - mutableMapOf( - "january" to "1", - "february" to "2", - "march" to "3", - "april" to "4", - "may" to "5", - "june" to "6", - "july" to "7", - "august" to "8", - "september" to "9", - "october" to "10", - "november" to "11", - "december" to "12" - ) - } - - override fun matches(value: String): Boolean { - val m = io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryDayNumber.Companion.pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return true - } - - override val dayNumberElement: String? - get() = if (segments.size >= 3) segments[2] else null - - override fun canProvideMonth(): Boolean { - return segments.size >= 5 - } - - override val monthElement: String? - get() = if (segments.size >= 5 && monthMap[segments[4].lowercase(Locale.getDefault())] != null - ) monthMap[segments[4].lowercase(Locale.getDefault())] - else null - - override fun canProvideDayOfWeek(): Boolean { - return true - } - - override val dayOfWeekElement: String - get() = "*" - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = canProvideMonth() - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = - "(every|each)\\s([0-9]?[0-9])(st|nd|rd|th)\\sof\\s(month|january|february|march|april|may|june|july|august|september|october|november|december)" - private val pattern: Pattern = Pattern.compile(io.github.yamilmedina.naturalkron.elementprovider.recurring.EveryDayNumber.Companion.PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumberStep.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumberStep.kt deleted file mode 100644 index 6ab673b..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryDayNumberStep.kt +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class EveryDayNumberStep : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return segments.size == 3 - } - - override val dayNumberElement: String - get() = if (segments.size > 2) String.format("*/%s", segments[2]) else "*" - - override fun canProvideMonth(): Boolean { - return true - } - - override val monthElement: String - get() = "*" - - override fun canProvideDayOfWeek(): Boolean { - return true - } - - override val dayOfWeekElement: String - get() = "*" - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = true - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "(every\\s?(3[0-1]|2[0-9]|1[1-9]|[1-9])?\\s?days)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryHour.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryHour.kt deleted file mode 100644 index d27bc37..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryHour.kt +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class EveryHour : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - if (m.group(i) != null) { - segments.add(m.group(i)) - } - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return false - } - - override val minuteElement: String? - get() = null - - override fun canProvideHour(): Boolean { - return segments.size > 0 - } - - override val hourElement: String? - get() { - if (canProvideHour()) { - return if (segments.size > 3) String.format("*/%s", segments[3]) else "*" - } - return null - } - - override fun canProvideDayNumber(): Boolean { - return false - } - - override val dayNumberElement: String? - get() = null - - override fun canProvideMonth(): Boolean { - return false - } - - override val monthElement: String? - get() = null - - override fun canProvideDayOfWeek(): Boolean { - return false - } - - override val dayOfWeekElement: String? - get() = null - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = true - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "(hourly|(every|each) ?([0-9]+)?\\shour)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMinute.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMinute.kt deleted file mode 100644 index 92f6c2e..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMinute.kt +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class EveryMinute : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - if (m.group(i) != null) { - segments.add(m.group(i)) - } - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return segments.size > 0 - } - - override val minuteElement: String? - get() { - if (canProvideMinute()) { - return if (segments.size > 3) String.format("*/%s", segments[3]) else "*" - } - return null - } - - override fun canProvideHour(): Boolean { - return canProvideMinute() - } - - override val hourElement: String - get() = "*" - - override fun canProvideDayNumber(): Boolean { - return false - } - - override val dayNumberElement: String? - get() = null - - override fun canProvideMonth(): Boolean { - return false - } - - override val monthElement: String? - get() = null - - override fun canProvideDayOfWeek(): Boolean { - return false - } - - override val dayOfWeekElement: String? - get() = null - - override val isMinuteElementLocked: Boolean - get() = true - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "((every|each) ?([0-9]+)?\\sminute)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonth.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonth.kt deleted file mode 100644 index 1b0f5d7..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonth.kt +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.* -import java.util.regex.Pattern - -internal class EveryMonth : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - private val monthMap: MutableMap by lazy { - mutableMapOf( - "january" to "1", - "february" to "2", - "march" to "3", - "april" to "4", - "may" to "5", - "june" to "6", - "july" to "7", - "august" to "8", - "september" to "9", - "october" to "10", - "november" to "11", - "december" to "12" - ) - } - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return true - } - - override val dayNumberElement: String - get() = "1" - - override fun canProvideMonth(): Boolean { - return true - } - - override val monthElement: String? - get() { - if ((segments.size > 0 && segments[0] == "monthly") - || (segments.size > 3 && segments[3] == "month") - ) { - return "*" - } - return if (segments.size > 3 && monthMap[segments[3].lowercase(Locale.getDefault())] != null - ) monthMap[segments[3].lowercase(Locale.getDefault())] - else null - } - - override fun canProvideDayOfWeek(): Boolean { - return true - } - - override val dayOfWeekElement: String - get() = "*" - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = - "(monthly|(every|each)\\s(month|january|february|march|april|may|june|july|august|september|october|november|december))" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonthStep.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonthStep.kt deleted file mode 100644 index 5a7df18..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryMonthStep.kt +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class EveryMonthStep : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return true - } - - override val dayNumberElement: String - get() = "0" - - override fun canProvideMonth(): Boolean { - return segments.size == 3 - } - - override val monthElement: String - get() = if (segments.size > 2) String.format("*/%s", segments[2]) else "*" - - override fun canProvideDayOfWeek(): Boolean { - return true - } - - override val dayOfWeekElement: String - get() = "*" - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = true - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "(every\\s?(1[1-2]|[1-9])?\\s?months)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryWeek.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryWeek.kt deleted file mode 100644 index f9960ac..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryWeek.kt +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class EveryWeek : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute(): Boolean { - return true - } - - override val minuteElement: String - get() = "0" - - override fun canProvideHour(): Boolean { - return true - } - - override val hourElement: String - get() = "0" - - override fun canProvideDayNumber(): Boolean { - return true - } - - override val dayNumberElement: String - get() = "*" - - override fun canProvideMonth(): Boolean { - return true - } - - override val monthElement: String - get() = "*" - - override fun canProvideDayOfWeek(): Boolean { - return segments.size > 0 - } - - override val dayOfWeekElement: String - get() = "0" - - override val isMinuteElementLocked: Boolean - get() = false - - override val isHourElementLocked: Boolean - get() = false - - override val isDayNumberElementLocked: Boolean - get() = false - - override val isMonthElementLocked: Boolean - get() = false - - override val isDayOfWeekElementLocked: Boolean - get() = false - - companion object { - private const val PATTERN = "(weekly|(every|each)\\sweek)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryYear.kt b/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryYear.kt deleted file mode 100644 index dd4355f..0000000 --- a/src/main/kotlin/io/github/yamilmedina/naturalkron/elementprovider/recurring/EveryYear.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.yamilmedina.naturalkron.elementprovider.recurring - -import io.github.yamilmedina.naturalkron.ExpressionElementProvider -import java.util.regex.Pattern - -internal class EveryYear : ExpressionElementProvider { - private val segments: MutableList = mutableListOf() - override fun matches(value: String): Boolean { - val m = pattern.matcher(value) - while (m.find()) { - for (i in 0..m.groupCount()) { - segments.add(m.group(i)) - } - } - return segments.size > 0 - } - - override fun canProvideMinute() = true - - override val minuteElement = "0" - - override fun canProvideHour() = true - - override val hourElement = "0" - - override fun canProvideDayNumber() = true - - override val dayNumberElement = "1" - - override fun canProvideMonth() = true - - override val monthElement = "1" - - override fun canProvideDayOfWeek() = true - - override val dayOfWeekElement = "*" - - override val isMinuteElementLocked = false - - override val isHourElementLocked = false - - override val isDayNumberElementLocked = false - - override val isMonthElementLocked = false - - override val isDayOfWeekElementLocked = false - - companion object { - private const val PATTERN = "(yearly|annually|(every|each) ?([0-9]+)?\\s?year)" - private val pattern: Pattern = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE) - } -} diff --git a/src/test/kotlin/io/github/yamilmedina/kron/NaturalKronParserTest.kt b/src/test/kotlin/io/github/yamilmedina/kron/NaturalKronParserTest.kt new file mode 100644 index 0000000..aaedff5 --- /dev/null +++ b/src/test/kotlin/io/github/yamilmedina/kron/NaturalKronParserTest.kt @@ -0,0 +1,71 @@ +package io.github.yamilmedina.kron + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +class NaturalKronParserTest { + + @ParameterizedTest + @EnumSource(TestParams::class) + fun testParser(params: TestParams) { + val actual = io.github.yamilmedina.kron.NaturalKronParser().parse(params.input) + assertEquals(params.expected, actual.toString(), "Failed for input: <${params.input}>") + } + + companion object { + + enum class TestParams(val input: String, val expected: String) { + CASE_1("@yearly", "0 0 1 1 *"), + CASE_2("@annually", "0 0 1 1 *"), + CASE_3("@monthly", "0 0 1 * *"), + CASE_4("@weekly", "0 0 * * 0"), + CASE_5("@daily", "0 0 * * *"), + CASE_6("@midnight", "0 0 * * *"), + CASE_7("@hourly", "0 * * * *"), + CASE_8("each day", "0 0 * * *"), + CASE_9("every day", "0 0 * * *"), + CASE_10("daily", "0 0 * * *"), + CASE_11("every day at 3 AM", "0 3 * * *"), + CASE_12("5am", "0 5 * * *"), + CASE_13("daily at 5am", "0 5 * * *"), + CASE_14("every friday at 5am", "0 5 * * 5"), + CASE_15("daily at 17:30", "30 17 * * *"), + CASE_16("every week", "0 0 * * 0"), + CASE_17("weekly", "0 0 * * 0"), + CASE_18("every minute", "* * * * *"), + CASE_19("every 5 minutes", "*/5 * * * *"), + CASE_20("every 30 minutes", "*/30 * * * *"), + CASE_21("every month", "0 0 1 * *"), + CASE_22("monthly", "0 0 1 * *"), + CASE_23("every Monday", "0 0 * * 1"), + CASE_24("every Wednesday", "0 0 * * 3"), + CASE_25("every Friday", "0 0 * * 5"), + CASE_26("every hour", "0 * * * *"), + CASE_27("every 6 hours", "0 */6 * * *"), + CASE_28("hourly", "0 * * * *"), + CASE_29("every year", "0 0 1 1 *"), + CASE_30("yearly", "0 0 1 1 *"), + CASE_31("annually", "0 0 1 1 *"), + CASE_32("every day at 9am", "0 9 * * *"), + CASE_33("every day at 5pm", "0 17 * * *"), + CASE_34("every day at 5:45pm", "45 17 * * *"), + CASE_35("every day at 17:00", "0 17 * * *"), + CASE_36("every day at 17:25", "25 17 * * *"), + CASE_37("5:15am every Tuesday", "15 5 * * 2"), + CASE_38("7pm every Thursday", "0 19 * * 4"), + CASE_39("every May", "0 0 1 5 *"), + CASE_40("every december", "0 0 1 12 *"), + CASE_41("midnight", "0 0 * * *"), + CASE_42("midnight on tuesdays", "0 0 * * 2"), + CASE_43("every 5 minutes on Tuesdays", "*/5 * * * 2"), + CASE_44("noon", "0 12 * * *"), + CASE_45("every 25th", "0 0 25 * *"), + CASE_46("every 3rd of January", "0 0 3 1 *"), + CASE_47("11pm every 3rd of december", "0 23 3 12 *"), + CASE_48("every day at 17:01", "1 17 * * *"), + CASE_49("every day at 5:01pm", "1 17 * * *"), + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParserTest.kt b/src/test/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParserTest.kt deleted file mode 100644 index a6d215f..0000000 --- a/src/test/kotlin/io/github/yamilmedina/naturalkron/NaturalKronExpressionParserTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.yamilmedina.naturalkron - -import java.time.DayOfWeek -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import kotlin.test.Test -import kotlin.test.assertEquals - -class NaturalKronExpressionParserTest { - - @Test - fun `given every monday is parsed, then returns every monday expression`() { - val expression = "every monday" - val parsed = NaturalKronExpressionParser().parse(expression) - - assertEquals(expression, parsed.toNaturalLanguage()?.lowercase()) - assertEquals(DayOfWeek.MONDAY.value.toString(), parsed.dayOfWeek) - } - - @Test - fun `given every friday at 1015am is parsed, then returns every monday expression`() { - val expression = "every friday at 10:15am" - val parsed = NaturalKronExpressionParser().parse(expression) - - val expectedKronExpressionEveryFridayAt1015am = "0 15 10 * * 5" - assertEquals(DayOfWeek.FRIDAY.value.toString(), parsed.dayOfWeek) - assertEquals(expectedKronExpressionEveryFridayAt1015am, parsed.toString()) - } - - @Test - fun `given every day at 9am is parsed, then returns every day expression with date`() { - val expression = "every day at 9am" - val parsed = NaturalKronExpressionParser().parse(expression) - - val expectedKronExpressionEveryFridayAt9am = "0 0 9 * * *" - assertEquals("*", parsed.dayOfWeek) - assertEquals(expectedKronExpressionEveryFridayAt9am, parsed.toString()) - } - - @Test - fun `given an invalid expression, then raise KronException`() { - val invalidExpression = "invalid expression" - assertThrows { - NaturalKronExpressionParser().parse(invalidExpression) - } - } - - @Test - fun `given a partial invalid expression, then parse valid part`() { - val invalidExpression = "invalid at 1st of the month" - assertDoesNotThrow { - val parsed = NaturalKronExpressionParser().parse(invalidExpression) - assertEquals("0 0 0 1 * *", parsed.toString()) - } - } - -} \ No newline at end of file