mirror of
https://github.com/InsanusMokrassar/krontab.git
synced 2024-11-26 12:08:50 +00:00
commit
6dc85d7e18
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: InsanusMokrassar
|
||||
|
||||
custom: ['https://paypal.me/InsanusMokrassar?locale.x=ru_RU']
|
3
.github/labeler.yml
vendored
Normal file
3
.github/labeler.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
code: "**/*.kt"
|
||||
gradle: "**/*.gradle"
|
||||
markdown: "**/*.md"
|
13
.github/workflows/greetings.yml
vendored
Normal file
13
.github/workflows/greetings.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Greetings
|
||||
|
||||
on: [pull_request, issues]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Welcome with your first issue'
|
||||
pr-message: 'Welcome with your first Pull Request'
|
18
.github/workflows/label.yml
vendored
Normal file
18
.github/workflows/label.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# This workflow will triage pull requests and apply a label based on the
|
||||
# paths that are modified in the pull request.
|
||||
#
|
||||
# To use this workflow, you will need to set up a .github/labeler.yml
|
||||
# file with configuration. For more information, see:
|
||||
# https://github.com/actions/labeler/blob/master/README.md
|
||||
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -7,6 +7,18 @@
|
||||
* Coroutines `1.3.2` -> `1.3.3`
|
||||
* Klock `1.7.3` -> `1.8.6`
|
||||
|
||||
### 0.2.3
|
||||
|
||||
* Updates in libraries:
|
||||
* Kotlin `1.3.70` -> `1.3.72`
|
||||
* Coroutines `1.3.5` -> `1.3.7`
|
||||
* Klock `1.10.0` -> `1.11.3`
|
||||
* A lot of KDocs added and fixed
|
||||
* `EverySecondScheduler` changed its building logic - now it is lazy with builder using
|
||||
* `KronScheduler#doOnce` was optimized: now it will be explicitly called once and return result of its calculations
|
||||
* `KronScheduler#doWhile` was rewritten to use `KronScheduler#doOnce` for calculations of `block` result
|
||||
* New `buildSchedule(String)` function as a shortcut for `createSimpleScheduler(String)`
|
||||
|
||||
### 0.2.2
|
||||
|
||||
* Updates in libraries:
|
||||
|
@ -15,9 +15,10 @@ buildscript {
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
|
||||
id "org.jetbrains.dokka" version "$dokka_version"
|
||||
}
|
||||
|
||||
project.version = "0.2.2"
|
||||
project.version = "0.2.3"
|
||||
project.group = "com.insanusmokrassar"
|
||||
|
||||
apply from: "publish.gradle"
|
||||
@ -29,6 +30,8 @@ repositories {
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
|
||||
apply from: './dokka.gradle'
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
|
42
dokka.gradle
Normal file
42
dokka.gradle
Normal file
@ -0,0 +1,42 @@
|
||||
dokka {
|
||||
outputFormat = 'html'
|
||||
|
||||
switch (true) {
|
||||
case project.hasProperty("DOKKA_PATH"):
|
||||
outputDirectory = project.property("DOKKA_PATH").toString()
|
||||
break
|
||||
case System.getenv("DOKKA_PATH") != null:
|
||||
outputDirectory = System.getenv("DOKKA_PATH")
|
||||
break
|
||||
}
|
||||
|
||||
multiplatform {
|
||||
global {
|
||||
perPackageOption {
|
||||
prefix = "com.insanusmokrassar"
|
||||
skipDeprecated = true
|
||||
includeNonPublic = true
|
||||
reportUndocumented = true
|
||||
}
|
||||
|
||||
sourceLink {
|
||||
path = "./"
|
||||
url = "https://github.com/InsanusMokrassar/krontab/blob/master/"
|
||||
lineSuffix = "#L"
|
||||
}
|
||||
}
|
||||
|
||||
common {
|
||||
targets = ["JVM", "JS"]
|
||||
sourceRoot { path = "src/commonMain" }
|
||||
}
|
||||
js {
|
||||
targets = ["JS"]
|
||||
sourceRoot { path = "src/jsMain" }
|
||||
}
|
||||
jvm {
|
||||
targets = ["JVM"]
|
||||
sourceRoot { path = "src/jvmMain" }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
kotlin.code.style=official
|
||||
kotlin_version=1.3.70
|
||||
kotlin_coroutines_version=1.3.5
|
||||
kotlin_version=1.3.72
|
||||
kotlin_coroutines_version=1.3.7
|
||||
|
||||
dokka_version=0.10.1
|
||||
|
||||
gradle_bintray_plugin_version=1.8.4
|
||||
|
||||
klockVersion=1.10.0
|
||||
klockVersion=1.11.3
|
||||
|
||||
kotlin.incremental.multiplatform=true
|
||||
|
@ -1,32 +1,53 @@
|
||||
package com.insanusmokrassar.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean) {
|
||||
do {
|
||||
delay(next().unixMillisLong - DateTime.now().unixMillisLong)
|
||||
} while (block())
|
||||
/**
|
||||
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
|
||||
*
|
||||
* WARNING!!! If you want to launch it in parallel, you must do this explicit.
|
||||
*/
|
||||
suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T {
|
||||
delay((next() - DateTime.now()).millisecondsLong)
|
||||
return block()
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [createSimpleScheduler] using [scheduleConfig] and call [doOnce] on it
|
||||
*/
|
||||
suspend inline fun <T> doOnce(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> T
|
||||
) = createSimpleScheduler(scheduleConfig).doOnce(block)
|
||||
|
||||
/**
|
||||
* Will execute [block] while it will return true as a result of its calculation
|
||||
*/
|
||||
suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean) {
|
||||
do { val doNext = doOnce(block) } while (doNext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [createSimpleScheduler] using [scheduleConfig] and call [doWhile] with [block]
|
||||
*/
|
||||
suspend inline fun doWhile(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> Boolean
|
||||
) = createSimpleScheduler(scheduleConfig).doWhile(block)
|
||||
|
||||
/**
|
||||
* Will execute [block] without any checking of result
|
||||
*/
|
||||
suspend inline fun KronScheduler.doInfinity(noinline block: suspend () -> Unit) = doWhile {
|
||||
block()
|
||||
true
|
||||
}
|
||||
/**
|
||||
* Will [createSimpleScheduler] using [scheduleConfig] and call [doInfinity] with [block]
|
||||
*/
|
||||
suspend inline fun doInfinity(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> Unit
|
||||
) = createSimpleScheduler(scheduleConfig).doInfinity(block)
|
||||
|
||||
suspend inline fun KronScheduler.doOnce(noinline block: suspend () -> Unit) = doWhile {
|
||||
block()
|
||||
false
|
||||
}
|
||||
suspend inline fun doOnce(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> Unit
|
||||
) = createSimpleScheduler(scheduleConfig).doOnce(block)
|
||||
|
@ -2,6 +2,20 @@ package com.insanusmokrassar.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
|
||||
/**
|
||||
* This interface was created for abstraction of [next] operation. Currently, there is only
|
||||
* [com.insanusmokrassar.krontab.internal.CronDateTimeScheduler] realisation of this interface inside of this library,
|
||||
* but you it is possible to create your own realisation of this interface for scheduling, for example, depending of
|
||||
* users activity or something like this
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.internal.CronDateTimeScheduler
|
||||
*/
|
||||
interface KronScheduler {
|
||||
|
||||
/**
|
||||
* @return Next [DateTime] when some action must be triggered according to settings of this instance
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.internal.CronDateTimeScheduler.next
|
||||
*/
|
||||
suspend fun next(relatively: DateTime = DateTime.now()): DateTime
|
||||
}
|
||||
|
@ -2,28 +2,50 @@ package com.insanusmokrassar.krontab
|
||||
|
||||
import com.insanusmokrassar.krontab.builder.buildSchedule
|
||||
import com.insanusmokrassar.krontab.internal.CronDateTime
|
||||
import com.insanusmokrassar.krontab.internal.CronDateTimeScheduler
|
||||
|
||||
internal val anyCronDateTime by lazy {
|
||||
CronDateTime()
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now]
|
||||
*/
|
||||
val AnyTimeScheduler: KronScheduler by lazy {
|
||||
CronDateTimeScheduler(listOf(anyCronDateTime))
|
||||
}
|
||||
val EverySecondScheduler: KronScheduler
|
||||
get() = AnyTimeScheduler
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one second
|
||||
*/
|
||||
val EverySecondScheduler: KronScheduler by lazy {
|
||||
buildSchedule { seconds { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one minute
|
||||
*/
|
||||
val EveryMinuteScheduler: KronScheduler by lazy {
|
||||
buildSchedule { minutes { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one hour
|
||||
*/
|
||||
val EveryHourScheduler: KronScheduler by lazy {
|
||||
buildSchedule { hours { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one day
|
||||
*/
|
||||
val EveryDayOfMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule { dayOfMonth { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one month
|
||||
*/
|
||||
val EveryMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule { months { 0 every 1 } }
|
||||
}
|
@ -5,27 +5,38 @@ import com.insanusmokrassar.krontab.internal.*
|
||||
/**
|
||||
* Parse [incoming] string and adapt according to next format: "* * * * *" where order of things:
|
||||
*
|
||||
* seconds
|
||||
* minutes
|
||||
* hours
|
||||
* dayOfMonth
|
||||
* month
|
||||
* * seconds
|
||||
* * minutes
|
||||
* * hours
|
||||
* * dayOfMonth
|
||||
* * month
|
||||
*
|
||||
* And each one have next format:
|
||||
*
|
||||
* {number},{number},...
|
||||
* `{number},{number},...`
|
||||
*
|
||||
* and {number} here is one of {int}-{int} OR {int}/{int} OR *\/{int} OR {int}.
|
||||
* and {number} here is one of
|
||||
*
|
||||
* Seconds ranges can be found in [com.insanusmokrassar.krontab.internal.secondsRange].
|
||||
* Minutes ranges can be found in [com.insanusmokrassar.krontab.internal.minutesRange].
|
||||
* Hours ranges can be found in [com.insanusmokrassar.krontab.internal.hoursRange].
|
||||
* Days of month ranges can be found in [com.insanusmokrassar.krontab.internal.dayOfMonthRange].
|
||||
* Months ranges can be found in [com.insanusmokrassar.krontab.internal.monthRange].
|
||||
* * {int}-{int}
|
||||
* * {int}/{int}
|
||||
* * */{int}
|
||||
* * {int}
|
||||
*
|
||||
* @sample "0/5 * * * *" for every five seconds triggering
|
||||
* @sample "0/15 30 * * *" for every 15th seconds in a half of each hour
|
||||
* @sample "1 2 3 4 5" for triggering in near first second of second minute of third hour of fourth day of may
|
||||
* Additional info about ranges can be found in follow accordance:
|
||||
*
|
||||
* * Seconds ranges can be found in [secondsRange]
|
||||
* * Minutes ranges can be found in [minutesRange]
|
||||
* * Hours ranges can be found in [hoursRange]
|
||||
* * Days of month ranges can be found in [dayOfMonthRange]
|
||||
* * Months ranges can be found in [monthRange]
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* * "0/5 * * * *" for every five seconds triggering
|
||||
* * "0/15 30 * * *" for every 15th seconds in a half of each hour
|
||||
* * "1 2 3 4 5" for triggering in near first second of second minute of third hour of fourth day of may
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.internal.createKronScheduler
|
||||
*/
|
||||
fun createSimpleScheduler(incoming: String): KronScheduler {
|
||||
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ")
|
||||
@ -36,27 +47,12 @@ fun createSimpleScheduler(incoming: String): KronScheduler {
|
||||
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
||||
val monthParsed = parseMonths(monthSource)
|
||||
|
||||
val resultCronDateTimes = mutableListOf(CronDateTime())
|
||||
|
||||
secondsParsed ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(seconds = currentTime)
|
||||
}
|
||||
|
||||
minutesParsed ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(minutes = currentTime)
|
||||
}
|
||||
|
||||
hoursParsed ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(hours = currentTime)
|
||||
}
|
||||
|
||||
dayOfMonthParsed ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(dayOfMonth = currentTime)
|
||||
}
|
||||
|
||||
monthParsed ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(month = currentTime)
|
||||
}
|
||||
|
||||
return CronDateTimeScheduler(resultCronDateTimes.toList())
|
||||
return createKronScheduler(
|
||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fun buildSchedule(incoming: String): KronScheduler = createSimpleScheduler(incoming)
|
@ -1,10 +1,16 @@
|
||||
package com.insanusmokrassar.krontab.builder
|
||||
|
||||
import com.insanusmokrassar.krontab.CronDateTimeScheduler
|
||||
import com.insanusmokrassar.krontab.KronScheduler
|
||||
import com.insanusmokrassar.krontab.internal.*
|
||||
import com.insanusmokrassar.krontab.internal.CronDateTime
|
||||
import com.insanusmokrassar.krontab.internal.CronDateTimeScheduler
|
||||
import com.insanusmokrassar.krontab.internal.fillWith
|
||||
|
||||
/**
|
||||
* Will help to create an instance of [KronScheduler]
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.createSimpleScheduler
|
||||
*/
|
||||
fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
||||
val builder = SchedulerBuilder()
|
||||
|
||||
@ -36,6 +42,9 @@ class SchedulerBuilder(
|
||||
} ?: builderValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an seconds block
|
||||
*/
|
||||
fun seconds(block: SecondsBuilder.() -> Unit) {
|
||||
seconds = callAndReturn(
|
||||
seconds,
|
||||
@ -44,6 +53,9 @@ class SchedulerBuilder(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an minutes block
|
||||
*/
|
||||
fun minutes(block: MinutesBuilder.() -> Unit) {
|
||||
minutes = callAndReturn(
|
||||
minutes,
|
||||
@ -52,6 +64,9 @@ class SchedulerBuilder(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an hours block
|
||||
*/
|
||||
fun hours(block: HoursBuilder.() -> Unit) {
|
||||
hours = callAndReturn(
|
||||
hours,
|
||||
@ -60,6 +75,9 @@ class SchedulerBuilder(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an days of month block
|
||||
*/
|
||||
fun dayOfMonth(block: DaysOfMonthBuilder.() -> Unit) {
|
||||
dayOfMonth = callAndReturn(
|
||||
dayOfMonth,
|
||||
@ -68,6 +86,9 @@ class SchedulerBuilder(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an months block
|
||||
*/
|
||||
fun months(block: MonthsBuilder.() -> Unit) {
|
||||
month = callAndReturn(
|
||||
month,
|
||||
@ -76,29 +97,11 @@ class SchedulerBuilder(
|
||||
)
|
||||
}
|
||||
|
||||
fun build(): KronScheduler {
|
||||
val resultCronDateTimes = mutableListOf(CronDateTime())
|
||||
|
||||
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(seconds = currentTime)
|
||||
}
|
||||
|
||||
minutes ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(minutes = currentTime)
|
||||
}
|
||||
|
||||
hours ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(hours = currentTime)
|
||||
}
|
||||
|
||||
dayOfMonth ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(dayOfMonth = currentTime)
|
||||
}
|
||||
|
||||
month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(month = currentTime)
|
||||
}
|
||||
|
||||
return CronDateTimeScheduler(resultCronDateTimes.toList())
|
||||
}
|
||||
/**
|
||||
* @return Completely built and independent [KronScheduler]
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.createSimpleScheduler
|
||||
* @see com.insanusmokrassar.krontab.internal.createKronScheduler
|
||||
*/
|
||||
fun build(): KronScheduler = createKronScheduler(seconds, minutes, hours, dayOfMonth, month)
|
||||
}
|
||||
|
@ -3,27 +3,53 @@ package com.insanusmokrassar.krontab.builder
|
||||
import com.insanusmokrassar.krontab.internal.*
|
||||
import com.insanusmokrassar.krontab.utils.clamp
|
||||
|
||||
/**
|
||||
* This class was created for incapsulation of builder work with specified [restrictionsRange]. For example,
|
||||
* [include] function of [TimeBuilder] will always [clamp] incoming data using its [restrictionsRange]
|
||||
*/
|
||||
sealed class TimeBuilder (
|
||||
private val restrictionsRange: IntRange
|
||||
) {
|
||||
private var result: Set<Int>? = null
|
||||
|
||||
/**
|
||||
* After calling of this function this builder will allow any value of current time
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun allowAll() {
|
||||
result = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Will include all variations from this array inside of this timeline
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
infix fun include(array: Array<Int>) {
|
||||
val clamped = array.map { it.clamp(restrictionsRange) } + (result ?: emptySet())
|
||||
result = clamped.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one [value] to current timeline
|
||||
*/
|
||||
@Suppress("unused")
|
||||
infix fun at(value: Int) {
|
||||
result = (result ?: emptySet()) + value.clamp(restrictionsRange)
|
||||
}
|
||||
|
||||
/**
|
||||
* Just wrapper for more obvious writing something like "[from] 2 [every] 5". For example, for [SecondsBuilder] it
|
||||
* will mean "[from] second second [every] 5 seconds", or "2, 7, 13, ..."
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun from(value: Int) = value
|
||||
inline infix fun from(value: Int) = value
|
||||
|
||||
/**
|
||||
* Will create an sequence of times starting [from] [this] [every] [delay] times. For example, for [SecondsBuilder] it
|
||||
* will mean "[from] second second [every] 5 seconds", or "2, 7, 13, ..."
|
||||
*
|
||||
* @see [from]
|
||||
*/
|
||||
infix fun Int.every(delay: Int): Array<Int> {
|
||||
val progression = clamp(restrictionsRange) .. restrictionsRange.last step delay
|
||||
val result = progression.toSet().toTypedArray()
|
||||
@ -32,8 +58,16 @@ sealed class TimeBuilder (
|
||||
|
||||
return result
|
||||
}
|
||||
infix fun every(delay: Int): Array<Int> = 0 every delay
|
||||
|
||||
/**
|
||||
* Shortcut for "[from] 0 [every] [delay]"
|
||||
*/
|
||||
infix fun every(delay: Int): Array<Int> = this from 0 every delay
|
||||
|
||||
/**
|
||||
* Will fill up this timeline from [this] up to [endIncluding]
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
infix fun Int.upTo(endIncluding: Int): Array<Int> {
|
||||
val progression = clamp(restrictionsRange) .. endIncluding.clamp(restrictionsRange)
|
||||
val result = progression.toSet().toTypedArray()
|
||||
@ -42,13 +76,17 @@ sealed class TimeBuilder (
|
||||
|
||||
return result
|
||||
}
|
||||
infix fun upTo(endIncluding: Int): Array<Int> = 0 upTo endIncluding
|
||||
/**
|
||||
* Shortcut for "[from] 0 [upTo] [endIncluding]"
|
||||
*/
|
||||
@Suppress("unused")
|
||||
infix fun upTo(endIncluding: Int): Array<Int> = this from 0 upTo endIncluding
|
||||
|
||||
internal fun build() = result ?.map { it.toByte() } ?.toTypedArray()
|
||||
}
|
||||
|
||||
class SecondsBuilder : TimeBuilder(secondsRange)
|
||||
class MinutesBuilder : TimeBuilder(minutesRange)
|
||||
class HoursBuilder : TimeBuilder(com.insanusmokrassar.krontab.internal.hoursRange)
|
||||
class DaysOfMonthBuilder : TimeBuilder(com.insanusmokrassar.krontab.internal.dayOfMonthRange)
|
||||
class HoursBuilder : TimeBuilder(hoursRange)
|
||||
class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange)
|
||||
class MonthsBuilder : TimeBuilder(monthRange)
|
||||
|
@ -1,15 +1,16 @@
|
||||
package com.insanusmokrassar.krontab.internal
|
||||
|
||||
import com.insanusmokrassar.krontab.KronScheduler
|
||||
import com.insanusmokrassar.krontab.utils.clamp
|
||||
import com.soywiz.klock.DateTime
|
||||
import com.soywiz.klock.DateTimeSpan
|
||||
|
||||
/**
|
||||
* [month] 0-11
|
||||
* [dayOfMonth] 0-31
|
||||
* [hours] 0-23
|
||||
* [minutes] 0-59
|
||||
* [seconds] 0-59
|
||||
* @param month 0-11
|
||||
* @param dayOfMonth 0-31
|
||||
* @param hours 0-23
|
||||
* @param minutes 0-59
|
||||
* @param seconds 0-59
|
||||
*/
|
||||
internal data class CronDateTime(
|
||||
val month: Byte? = null,
|
||||
@ -19,16 +20,24 @@ internal data class CronDateTime(
|
||||
val seconds: Byte? = null
|
||||
) {
|
||||
init {
|
||||
check(month ?.let { it in com.insanusmokrassar.krontab.internal.monthRange } ?: true)
|
||||
check(dayOfMonth ?.let { it in com.insanusmokrassar.krontab.internal.dayOfMonthRange } ?: true)
|
||||
check(hours?.let { it in com.insanusmokrassar.krontab.internal.hoursRange } ?: true)
|
||||
check(minutes?.let { it in com.insanusmokrassar.krontab.internal.minutesRange } ?: true)
|
||||
check(seconds?.let { it in com.insanusmokrassar.krontab.internal.secondsRange } ?: true)
|
||||
check(month ?.let { it in monthRange } ?: true)
|
||||
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true)
|
||||
check(hours?.let { it in hoursRange } ?: true)
|
||||
check(minutes?.let { it in minutesRange } ?: true)
|
||||
check(seconds?.let { it in secondsRange } ?: true)
|
||||
}
|
||||
|
||||
internal val klockDayOfMonth = dayOfMonth ?.plus(1)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Using [clamp] extension for checking every parameter to be ensure that they are all correct
|
||||
* @param month 0-11
|
||||
* @param dayOfMonth 0-31
|
||||
* @param hours 0-23
|
||||
* @param minutes 0-59
|
||||
* @param seconds 0-59
|
||||
*/
|
||||
fun create(
|
||||
month: Int? = null,
|
||||
dayOfMonth: Int? = null,
|
||||
@ -36,15 +45,18 @@ internal data class CronDateTime(
|
||||
minutes: Int? = null,
|
||||
seconds: Int? = null
|
||||
) = CronDateTime(
|
||||
month ?.clamp(com.insanusmokrassar.krontab.internal.monthRange) ?.toByte(),
|
||||
dayOfMonth ?.clamp(com.insanusmokrassar.krontab.internal.dayOfMonthRange) ?.toByte(),
|
||||
hours ?.clamp(com.insanusmokrassar.krontab.internal.hoursRange) ?.toByte(),
|
||||
minutes ?.clamp(com.insanusmokrassar.krontab.internal.minutesRange) ?.toByte(),
|
||||
seconds ?.clamp(com.insanusmokrassar.krontab.internal.secondsRange) ?.toByte()
|
||||
month ?.clamp(monthRange) ?.toByte(),
|
||||
dayOfMonth ?.clamp(dayOfMonthRange) ?.toByte(),
|
||||
hours ?.clamp(hoursRange) ?.toByte(),
|
||||
minutes ?.clamp(minutesRange) ?.toByte(),
|
||||
seconds ?.clamp(secondsRange) ?.toByte()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The near [DateTime] which happens after [relativelyTo] or will be equal to [relativelyTo]
|
||||
*/
|
||||
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime {
|
||||
var current = relativelyTo
|
||||
|
||||
@ -75,3 +87,38 @@ internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
||||
*/
|
||||
internal fun createKronScheduler(
|
||||
seconds: Array<Byte>? = null,
|
||||
minutes: Array<Byte>? = null,
|
||||
hours: Array<Byte>? = null,
|
||||
dayOfMonth: Array<Byte>? = null,
|
||||
month: Array<Byte>? = null
|
||||
): KronScheduler {
|
||||
val resultCronDateTimes = mutableListOf(CronDateTime())
|
||||
|
||||
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(seconds = currentTime)
|
||||
}
|
||||
|
||||
minutes ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(minutes = currentTime)
|
||||
}
|
||||
|
||||
hours ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(hours = currentTime)
|
||||
}
|
||||
|
||||
dayOfMonth ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(dayOfMonth = currentTime)
|
||||
}
|
||||
|
||||
month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||
previousCronDateTime.copy(month = currentTime)
|
||||
}
|
||||
|
||||
return CronDateTimeScheduler(resultCronDateTimes.toList())
|
||||
}
|
||||
|
@ -1,12 +1,30 @@
|
||||
package com.insanusmokrassar.krontab
|
||||
package com.insanusmokrassar.krontab.internal
|
||||
|
||||
import com.insanusmokrassar.krontab.internal.CronDateTime
|
||||
import com.insanusmokrassar.krontab.internal.toNearDateTime
|
||||
import com.insanusmokrassar.krontab.KronScheduler
|
||||
import com.insanusmokrassar.krontab.anyCronDateTime
|
||||
import com.soywiz.klock.DateTime
|
||||
|
||||
/**
|
||||
* Cron-oriented realisation of [KronScheduler]
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.AnyTimeScheduler
|
||||
* @see com.insanusmokrassar.krontab.EverySecondScheduler
|
||||
* @see com.insanusmokrassar.krontab.EveryMinuteScheduler
|
||||
* @see com.insanusmokrassar.krontab.EveryHourScheduler
|
||||
* @see com.insanusmokrassar.krontab.EveryDayOfMonthScheduler
|
||||
* @see com.insanusmokrassar.krontab.EveryMonthScheduler
|
||||
*
|
||||
* @see com.insanusmokrassar.krontab.builder.buildSchedule
|
||||
* @see com.insanusmokrassar.krontab.builder.SchedulerBuilder
|
||||
*/
|
||||
internal data class CronDateTimeScheduler internal constructor(
|
||||
internal val cronDateTimes: List<CronDateTime>
|
||||
) : KronScheduler {
|
||||
/**
|
||||
* @return Near date using [cronDateTimes] list and getting the [Iterable.min] one
|
||||
*
|
||||
* @see toNearDateTime
|
||||
*/
|
||||
override suspend fun next(relatively: DateTime): DateTime {
|
||||
return cronDateTimes.map { it.toNearDateTime(relatively) }.min() ?: anyCronDateTime.toNearDateTime(relatively)
|
||||
}
|
||||
|
@ -1,4 +1,12 @@
|
||||
package com.insanusmokrassar.krontab.utils
|
||||
|
||||
/**
|
||||
* @return [min] in case if [this] less than [min]. Otherwise will check that [max] grant than [this] and return [this]
|
||||
* if so or [max] otherwise
|
||||
*/
|
||||
internal fun Int.clamp(min: Int, max: Int): Int = if (this < min) min else if (this > max) max else this
|
||||
|
||||
/**
|
||||
* Wrapper function for [clamp] extension
|
||||
*/
|
||||
internal fun Int.clamp(range: IntRange): Int = clamp(range.first, range.last)
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.insanusmokrassar.krontab.utils
|
||||
|
||||
import com.insanusmokrassar.krontab.buildSchedule
|
||||
import com.insanusmokrassar.krontab.builder.buildSchedule
|
||||
import com.insanusmokrassar.krontab.createSimpleScheduler
|
||||
import com.soywiz.klock.DateTime
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@FlowPreview
|
||||
class StringParseTest {
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
|
||||
val kronScheduler = buildSchedule("*/1 * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
|
||||
runTest {
|
||||
val mustBeCollected = 10
|
||||
var collected = 0
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
collected++
|
||||
}
|
||||
assertEquals(mustBeCollected, collected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
|
||||
val kronScheduler = buildSchedule("*/1 * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
|
||||
runTest {
|
||||
val testsCount = 10
|
||||
val failJob = it.createFailJob((testsCount * 2) * 1000L)
|
||||
val mustBeCollected = 10
|
||||
val answers = (0 until testsCount).map { _ ->
|
||||
it.async {
|
||||
var collected = 0
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
collected++
|
||||
}
|
||||
collected
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
failJob.cancel()
|
||||
|
||||
answers.forEach {
|
||||
assertEquals(mustBeCollected, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user