Compare commits

...

28 Commits
0.2.1 ... 0.2.3

Author SHA1 Message Date
07e6baba3d Revert "fix of travis build script"
This reverts commit eb11235360.
2020-06-03 22:11:44 +06:00
d8ed07da98 new buildSchedule function and tests for string parsing 2020-06-03 22:04:05 +06:00
c9243d7968 optimization and docs of executes 2020-06-03 21:53:55 +06:00
eb11235360 fix of travis build script 2020-06-03 21:42:30 +06:00
565aff4538 fix for EverySecondScheduler 2020-06-03 21:39:52 +06:00
686716e20f TimeBuilder docs 2020-06-03 21:36:01 +06:00
6077384a17 fill documentation and optimize SchedulerBuilder#build and createSimpleScheduler functions 2020-06-03 21:21:52 +06:00
408349be04 docs 2020-06-03 21:07:10 +06:00
237deff0e6 fill docs of SchedulerShortcuts 2020-06-03 20:41:54 +06:00
59cd600d60 update CronDateTime documentation 2020-06-03 20:19:57 +06:00
be33a130d3 add data docs 2020-06-03 20:17:00 +06:00
1f971117bd refactor of CronDateTime 2020-06-03 20:11:42 +06:00
0178ac140a add dokka config 2020-06-03 20:10:06 +06:00
69e2e10d29 small optimization of Executes 2020-06-03 20:03:04 +06:00
25901b7ab6 add actions configs 2020-06-03 19:02:59 +06:00
7892a6897e start 0.2.3 and update dependencies 2020-06-03 18:59:58 +06:00
ef436892aa optimize imports 2020-03-22 18:35:25 +06:00
85cb87c459 version 0.2.2 2020-03-22 18:35:06 +06:00
75f6e06552 include build status from travis badge 2020-02-19 15:12:53 +06:00
e600d5fec9 update travis config to temporaly avoid of scanning 2020-02-19 15:09:54 +06:00
f480873cf0 add travis build script 2020-02-18 19:56:08 +06:00
363aeed732 Add maven link badge 2020-02-07 11:26:43 +00:00
18e0af4592 Add '_config.yml' 2020-02-07 11:21:05 +00:00
5c0e02220f update publish script 2020-01-15 21:04:03 +06:00
d516c26381 update publish script 2020-01-15 20:39:39 +06:00
10ee1672bc fix README 2020-01-14 14:53:36 +06:00
b564c36359 update publish gradle 2020-01-13 23:23:13 +06:00
394522db6b Merge branch '0.2.1' of InsanusMokrassar/krontab into master 2020-01-13 16:02:27 +00:00
23 changed files with 475 additions and 126 deletions

5
.github/FUNDING.yml vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
code: "**/*.kt"
gradle: "**/*.gradle"
markdown: "**/*.md"

13
.github/workflows/greetings.yml vendored Normal file
View 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
View 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 }}"

10
.travis.yml Normal file
View File

@@ -0,0 +1,10 @@
language: java
install: true
os: linux
dist: trusty
jdk: oraclejdk8
script:
- ./gradlew build allTests -s

View File

@@ -1,12 +1,31 @@
# Changelog
# 0.2.0
## 0.2.0
* Updated way of publishing (for more info look at the [git](https://git.insanusmokrassar.com/InsanusMokrassar/krontab))
* Updates in libraries:
* Coroutines `1.3.2` -> `1.3.3`
* Klock `1.7.3` -> `1.8.6`
## 0.2.1
### 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:
* Kotlin `1.3.61` -> `1.3.70`
* Coroutines `1.3.3` -> `1.3.5`
* Klock `1.8.6` -> `1.10.0`
### 0.2.1
* Added support of flows: now any `KronScheduler` can be convert to `Flow<DateTime>` using `asFlow` extension

View File

@@ -1,6 +1,8 @@
# krontab
[ ![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab/_latestVersion)
[ ![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.insanusmokrassar/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.insanusmokrassar/krontab)
[![Build Status](https://travis-ci.com/InsanusMokrassar/krontab.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/krontab)
Library was created to give oppotunity to launch some things from time to time according to some schedule in
runtime of applications.
@@ -140,8 +142,8 @@ val kronScheduler = buildSchedule {
val flow = kronScheduler.asFlow()
```
So, in this case any operations related to flow are available and it is expected tt th will work correctly. For example,
it is possible to use this flow with `takeWhile`:
So, in this case any operations related to flow are available and it is expected that they will work correctly. For
example, it is possible to use this flow with `takeWhile`:
```kotlin
flow.takeWhile {

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -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.1"
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
View 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" }
}
}
}

View File

@@ -1,9 +1,11 @@
kotlin.code.style=official
kotlin_version=1.3.61
kotlin_coroutines_version=1.3.3
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.8.6
klockVersion=1.11.3
kotlin.incremental.multiplatform=true

View File

@@ -2,26 +2,25 @@ apply plugin: 'com.jfrog.bintray'
apply from: "maven.publish.gradle"
ext {
projectBintrayDir = "${project.group}/".replace(".", "/") + "${project.name}/${project.version}"
}
bintray {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
File file = it.getFile()
String directorySubname = file.parentFile.name
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
it.exclude()
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
@@ -31,6 +30,7 @@ bintray {
name = "${project.name}"
vcsUrl = "https://git.insanusmokrassar.com/InsanusMokrassar/krontab"
licenses = ["Apache-2.0"]
override=true
version {
name = "${project.version}"
released = new Date()
@@ -45,8 +45,12 @@ bintray {
bintrayUpload.doFirst {
publications = publishing.publications.collect {
it.name
}
if (it.name.contains('kotlinMultiplatform')) {
null
} else {
it.name
}
} - null
}
bintrayUpload.dependsOn publishToMavenLocal

View File

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

View File

@@ -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
}

View File

@@ -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 } }
}

View File

@@ -1,37 +1,42 @@
package com.insanusmokrassar.krontab
import com.insanusmokrassar.krontab.internal.*
import com.insanusmokrassar.krontab.internal.CronDateTime
import com.insanusmokrassar.krontab.internal.parseDaysOfMonth
import com.insanusmokrassar.krontab.internal.parseHours
import com.insanusmokrassar.krontab.internal.parseMinutes
import com.insanusmokrassar.krontab.internal.parseMonths
import com.insanusmokrassar.krontab.internal.parseSeconds
/**
* 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}
* * *&#47;{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(" ")
@@ -42,27 +47,12 @@ fun createSimpleScheduler(incoming: String): KronScheduler {
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
val monthParsed = parseMonths(monthSource)
val resultCronDateTimes = mutableListOf(CronDateTime())
return createKronScheduler(
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed
)
}
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())
}
/**
*
*/
fun buildSchedule(incoming: String): KronScheduler = createSimpleScheduler(incoming)

View File

@@ -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)
}

View File

@@ -3,26 +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)
}
inline fun from(value: Int) = value
/**
* 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 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()
@@ -31,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()
@@ -41,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)

View File

@@ -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())
}

View File

@@ -1,11 +1,30 @@
package com.insanusmokrassar.krontab
package com.insanusmokrassar.krontab.internal
import com.insanusmokrassar.krontab.internal.*
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)
}

View File

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

View File

@@ -2,7 +2,8 @@ package com.insanusmokrassar.krontab.utils
import com.insanusmokrassar.krontab.KronScheduler
import com.soywiz.klock.DateTime
import kotlinx.coroutines.*
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@FlowPreview

View File

@@ -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)
}
}
}
}