diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6e8937..f16dc1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - java: [ 'adopt@1.8', 'adopt@1.11', 'openjdk@1.11', 'zulu@1.16' ] + java: [ 8, 11, 17 ] steps: - uses: actions/checkout@v2.3.4 with: diff --git a/.scala-steward.conf b/.scala-steward.conf deleted file mode 100644 index e2daf47..0000000 --- a/.scala-steward.conf +++ /dev/null @@ -1,3 +0,0 @@ -updates.pin = [ - { groupId = "co.fs2", version = "2." } -] \ No newline at end of file diff --git a/README.md b/README.md index 8216b72..b75d0b7 100644 --- a/README.md +++ b/README.md @@ -54,15 +54,6 @@ compared to systemd: ```sbt libraryDependencies += "com.github.eikek" %% "calev-core" % "0.5.4" ``` -- The *fs2* module contains utilities to work with - [FS2](https://github.com/functional-streams-for-scala/fs2) streams. - These were taken, thankfully and slightly modified to exchange cron expressions - for calendar events, from the - [fs2-cron](https://github.com/fthomas/fs2-cron) library. It is also published - for ScalaJS. With sbt, use - ```sbt - libraryDependencies += "com.github.eikek" %% "calev-fs2" % "0.5.4" - ``` - The *doobie* module contains `Meta`, `Read` and `Write` instances for `CalEvent` to use with [doobie](https://github.com/tpolecat/doobie). @@ -85,6 +76,10 @@ compared to systemd: libraryDependencies += "com.github.eikek" %% "calev-akka" % "0.5.4" ``` +Note that the fs2 module has been removed. The functionality is now +available for fs2 3.x from the +[fs2-cron](https://github.com/fthomas/fs2-cron) library. If calev-fs2 +is required for fs2 2.x, calev version 0.5.4 can be used. ## Examples @@ -167,16 +162,16 @@ import java.time._ ce.asString // res4: String = "*-*-* 00/2:00:00" val now = LocalDateTime.now -// now: LocalDateTime = 2021-06-30T00:21:12.170 +// now: LocalDateTime = 2021-08-20T18:50:58.888 ce.nextElapse(now) -// res5: Option[LocalDateTime] = Some(value = 2021-06-30T02:00) +// res5: Option[LocalDateTime] = Some(value = 2021-08-20T20:00) ce.nextElapses(now, 5) // res6: List[LocalDateTime] = List( -// 2021-06-30T02:00, -// 2021-06-30T04:00, -// 2021-06-30T06:00, -// 2021-06-30T08:00, -// 2021-06-30T10:00 +// 2021-08-20T20:00, +// 2021-08-20T22:00, +// 2021-08-21T00:00, +// 2021-08-21T02:00, +// 2021-08-21T04:00 // ) ``` @@ -188,54 +183,6 @@ CalEvent.unsafe("1900-01-* 12,14:0:0").nextElapse(LocalDateTime.now) ``` -### FS2 - -The fs2 utilities allow to schedule things based on calendar events. -This is the same as [fs2-cron](https://github.com/fthomas/fs2-cron) -provides, only adopted to use calendar events instead of cron -expressions. The example is also from there. - -**Note:** `calev-fs2` is still build against fs2 2.x. This module will -be removed in the future, because the -[fs2-cron](https://github.com/fthomas/fs2-cron) project now provides -this via its `fs2-cron-calev` module, which is built against fs2 3 -already. - -```scala -import cats.effect.{IO, Timer} -import fs2.Stream -import com.github.eikek.fs2calev._ -import java.time.LocalTime -import scala.concurrent.ExecutionContext - -implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global) -// timer: Timer[IO] = cats.effect.internals.IOTimer@4406d447 - -val printTime = IO(println(LocalTime.now)) -// printTime: IO[Unit] = Delay(thunk = ) - -val event = CalEvent.unsafe("*-*-* *:*:0/2") -// event: CalEvent = CalEvent( -// weekday = All, -// date = DateEvent(year = All, month = All, day = All), -// time = TimeEvent( -// hour = All, -// minute = All, -// seconds = List(values = Vector(Single(value = 0, rep = Some(value = 2)))) -// ), -// zone = None -// ) - -val task = CalevFs2.awakeEvery[IO](event).evalMap(_ => printTime) -// task: Stream[IO[x], Unit] = Stream(..) - -task.take(3).compile.drain.unsafeRunSync() -// 00:21:14.021 -// 00:21:16 -// 00:21:18.001 -``` - - ### Doobie When using doobie, this module contains instances to write and read @@ -270,8 +217,8 @@ val insert = // acquire = Suspend( // a = PrepareStatement(a = "INSERT INTO mytable (event) VALUES (?)") // ), -// use = doobie.hi.connection$$$Lambda$11625/2046523540@5c2fb3a6, -// release = cats.effect.Bracket$$Lambda$11627/540350284@8c0f281 +// use = doobie.hi.connection$$$Lambda$38918/2009939920@2835fff2, +// release = cats.effect.Bracket$$Lambda$38920/1634253453@461bcb5d // ) // ) @@ -282,8 +229,8 @@ val select = // acquire = Suspend( // a = PrepareStatement(a = "SELECT event FROM mytable WHERE id = 1") // ), -// use = doobie.hi.connection$$$Lambda$11625/2046523540@1c03aa81, -// release = cats.effect.Bracket$$Lambda$11627/540350284@430a5324 +// use = doobie.hi.connection$$$Lambda$38918/2009939920@3277d9fb, +// release = cats.effect.Bracket$$Lambda$38920/1634253453@1e9b8fb8 // ) // ) ``` @@ -357,6 +304,8 @@ val read = for { // ) // ) ``` + + ### Jackson Add `CalevModule` to use calendar event expressions in json: @@ -371,7 +320,7 @@ val jackson = JsonMapper .builder() .addModule(new CalevModule()) .build() -// jackson: JsonMapper = com.fasterxml.jackson.databind.json.JsonMapper@62197a60 +// jackson: JsonMapper = com.fasterxml.jackson.databind.json.JsonMapper@10f58b2 val myEvent = CalEvent.unsafe("Mon *-*-* 05:00/10:00") // myEvent: CalEvent = CalEvent( @@ -399,6 +348,8 @@ val eventDeserialized = jackson.readValue(eventSerialized, new TypeReference[Cal // zone = None // ) ``` + + ### Akka #### Akka Timers @@ -437,7 +388,7 @@ CalevBehaviors.withCalevTimers[Message]() { scheduler => same } } -// res9: Behavior[Message] = Deferred(TimerSchedulerImpl.scala:29) +// res8: Behavior[Message] = Deferred(TimerSchedulerImpl.scala:29) ``` Use ```CalevBehaviors.withCalendarEvent``` to schedule messages according @@ -457,8 +408,9 @@ CalevBehaviors.withCalendarEvent(calEvent)( same } ) -// res10: Behavior[Message] = Deferred(InterceptorImpl.scala:29-30) +// res9: Behavior[Message] = Deferred(InterceptorImpl.scala:29-30) ``` + #### Testing See [CalevBehaviorsTest](https://github.com/eikek/calev/blob/master/modules/akka/src/test/scala/com/github/eikek/calev/akka/dsl/CalevBehaviorsTest.scala) @@ -489,8 +441,8 @@ calevScheduler().scheduleOnceWithCalendarEvent(calEvent, () => { s"Called at: ${LocalTime.now}" ) }) -// res11: Option[..akka.actor.Cancellable] = Some( -// value = akka.actor.LightArrayRevolverScheduler$TaskHolder@48d6430 +// res10: Option[..akka.actor.Cancellable] = Some( +// value = akka.actor.LightArrayRevolverScheduler$TaskHolder@2deff253 // ) system.terminate() -``` \ No newline at end of file +``` diff --git a/build.sbt b/build.sbt index e0de9ac..450cd5e 100644 --- a/build.sbt +++ b/build.sbt @@ -122,21 +122,6 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) lazy val coreJVM = core.jvm lazy val coreJS = core.js -lazy val fs2 = crossProject(JSPlatform, JVMPlatform) - .crossType(CrossType.Pure) - .in(file("modules/fs2")) - .settings(sharedSettings) - .settings(testSettings) - .settings(scalafixSettings) - .settings( - name := "calev-fs2", - libraryDependencies ++= - Dependencies.fs2 - ) - .dependsOn(core) -lazy val fs2JVM = fs2.jvm -lazy val fs2JS = fs2.js - lazy val doobieJVM = project .in(file("modules/doobie")) .settings(sharedSettings) @@ -226,7 +211,7 @@ lazy val readme = project () } ) - .dependsOn(coreJVM, fs2JVM, doobieJVM, circeJVM, jacksonJVM, akkaJVM) + .dependsOn(coreJVM, doobieJVM, circeJVM, jacksonJVM, akkaJVM) val root = project .in(file(".")) @@ -239,8 +224,6 @@ val root = project .aggregate( coreJVM, coreJS, - fs2JVM, - fs2JS, doobieJVM, circeJVM, circeJS, diff --git a/docs/readme.md b/docs/readme.md index a51d529..f57589a 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -54,15 +54,6 @@ compared to systemd: ```sbt libraryDependencies += "com.github.eikek" %% "calev-core" % "@VERSION@" ``` -- The *fs2* module contains utilities to work with - [FS2](https://github.com/functional-streams-for-scala/fs2) streams. - These were taken, thankfully and slightly modified to exchange cron expressions - for calendar events, from the - [fs2-cron](https://github.com/fthomas/fs2-cron) library. It is also published - for ScalaJS. With sbt, use - ```sbt - libraryDependencies += "com.github.eikek" %% "calev-fs2" % "@VERSION@" - ``` - The *doobie* module contains `Meta`, `Read` and `Write` instances for `CalEvent` to use with [doobie](https://github.com/tpolecat/doobie). @@ -85,6 +76,10 @@ compared to systemd: libraryDependencies += "com.github.eikek" %% "calev-akka" % "@VERSION@" ``` +Note that the fs2 module has been removed. The functionality is now +available for fs2 3.x from the +[fs2-cron](https://github.com/fthomas/fs2-cron) library. If calev-fs2 +is required for fs2 2.x, calev version 0.5.4 can be used. ## Examples @@ -134,38 +129,6 @@ CalEvent.unsafe("1900-01-* 12,14:0:0").nextElapse(LocalDateTime.now) ``` -### FS2 - -The fs2 utilities allow to schedule things based on calendar events. -This is the same as [fs2-cron](https://github.com/fthomas/fs2-cron) -provides, only adopted to use calendar events instead of cron -expressions. The example is also from there. - -**Note:** `calev-fs2` is still build against fs2 2.x. This module will -be removed in the future, because the -[fs2-cron](https://github.com/fthomas/fs2-cron) project now provides -this via its `fs2-cron-calev` module, which is built against fs2 3 -already. - -```scala mdoc -import cats.effect.{IO, Timer} -import fs2.Stream -import com.github.eikek.fs2calev._ -import java.time.LocalTime -import scala.concurrent.ExecutionContext - -implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global) - -val printTime = IO(println(LocalTime.now)) - -val event = CalEvent.unsafe("*-*-* *:*:0/2") - -val task = CalevFs2.awakeEvery[IO](event).evalMap(_ => printTime) - -task.take(3).compile.drain.unsafeRunSync() -``` - - ### Doobie When using doobie, this module contains instances to write and read @@ -214,6 +177,8 @@ val read = for { value <- parsed.as[Meeting] } yield value ``` + + ### Jackson Add `CalevModule` to use calendar event expressions in json: @@ -234,6 +199,8 @@ val myEvent = CalEvent.unsafe("Mon *-*-* 05:00/10:00") val eventSerialized = jackson.writeValueAsString(myEvent) val eventDeserialized = jackson.readValue(eventSerialized, new TypeReference[CalEvent] {}) ``` + + ### Akka #### Akka Timers @@ -292,6 +259,7 @@ CalevBehaviors.withCalendarEvent(calEvent)( } ) ``` + #### Testing See [CalevBehaviorsTest](https://github.com/eikek/calev/blob/master/modules/akka/src/test/scala/com/github/eikek/calev/akka/dsl/CalevBehaviorsTest.scala) @@ -322,4 +290,4 @@ calevScheduler().scheduleOnceWithCalendarEvent(calEvent, () => { ) }) system.terminate() -``` \ No newline at end of file +``` diff --git a/modules/core/src/test/scala/com/github/eikek/calev/CalEventTest.scala b/modules/core/src/test/scala/com/github/eikek/calev/CalEventTest.scala index 3fcc554..cee4400 100644 --- a/modules/core/src/test/scala/com/github/eikek/calev/CalEventTest.scala +++ b/modules/core/src/test/scala/com/github/eikek/calev/CalEventTest.scala @@ -5,17 +5,11 @@ import java.time.LocalTime import java.time.ZonedDateTime import java.time.temporal.ChronoField -import scala.concurrent.ExecutionContext - -import cats.effect._ import com.github.eikek.calev.Dsl._ import munit._ class CalEventTest extends FunSuite { - implicit val CS: ContextShift[IO] = IO.contextShift(ExecutionContext.global) - val blocker = Blocker.liftExecutionContext(ExecutionContext.global) - test("contains") { val ce = CalEvent(Mon ~ Tue, DateEvent.All, time(0.c, 10.c ++ 20.c, 0.c)) assert(ce.contains(zdt(2015, 10, 12, 0, 10, 0))) diff --git a/modules/core/src/test/scala/com/github/eikek/calev/TestDataSet.scala b/modules/core/src/test/scala/com/github/eikek/calev/TestDataSet.scala index 594d153..5dbf047 100644 --- a/modules/core/src/test/scala/com/github/eikek/calev/TestDataSet.scala +++ b/modules/core/src/test/scala/com/github/eikek/calev/TestDataSet.scala @@ -12,25 +12,19 @@ case class TestDataSet(event: CalEvent, ref: ZonedDateTime, expect: List[ZonedDa object TestDataSet { type EA[B] = Either[Throwable, B] - def readResource[F[_]: Sync: ContextShift]( - name: String, - blocker: Blocker - ): Stream[F, EA[TestDataSet]] = + def readResource[F[_]: Sync](name: String): Stream[F, EA[TestDataSet]] = Option(getClass.getResource(name)) match { case Some(url) => - read[F](url, blocker) + read[F](url) case None => sys.error(s"Resource not found: $name") } - def read[F[_]: Sync: ContextShift]( - url: URL, - blocker: Blocker - ): Stream[F, EA[TestDataSet]] = + def read[F[_]: Sync](url: URL): Stream[F, EA[TestDataSet]] = fs2.io - .readInputStream(Sync[F].delay(url.openStream), 8192, blocker) - .through(fs2.text.utf8Decode) + .readInputStream(Sync[F].delay(url.openStream), 8192) + .through(fs2.text.utf8.decode) .through(fs2.text.lines) .filter(l => !l.trim.startsWith("#")) .split(_.trim.isEmpty) diff --git a/modules/core/src/test/scala/com/github/eikek/calev/TriggerDataTest.scala b/modules/core/src/test/scala/com/github/eikek/calev/TriggerDataTest.scala index c2d25d3..e832a53 100644 --- a/modules/core/src/test/scala/com/github/eikek/calev/TriggerDataTest.scala +++ b/modules/core/src/test/scala/com/github/eikek/calev/TriggerDataTest.scala @@ -1,24 +1,20 @@ package com.github.eikek.calev -import scala.concurrent.ExecutionContext - import cats.effect._ +import cats.effect.unsafe.implicits._ import munit._ class TriggerDataTest extends FunSuite { - implicit val CS: ContextShift[IO] = IO.contextShift(ExecutionContext.global) val resource = "trigger-data.txt" - val data = Blocker[IO] - .use { blocker => - TestDataSet - .readResource[IO](resource, blocker) - .zipWithIndex - .compile - .toVector - } - .unsafeRunSync() + val data = + TestDataSet + .readResource[IO](resource) + .zipWithIndex + .compile + .toVector + .unsafeRunSync() data.foreach { case (Left(ex), index) => diff --git a/modules/fs2/src/main/scala/com/github/eikek/fs2calev/CalevFs2.scala b/modules/fs2/src/main/scala/com/github/eikek/fs2calev/CalevFs2.scala deleted file mode 100644 index 48329cf..0000000 --- a/modules/fs2/src/main/scala/com/github/eikek/fs2calev/CalevFs2.scala +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.eikek.fs2calev - -import java.time.ZonedDateTime -import java.time.temporal.ChronoUnit -import java.util.concurrent.TimeUnit - -import scala.concurrent.duration.FiniteDuration - -import _root_.fs2._ -import cats.ApplicativeError -import cats.effect._ -import cats.implicits._ -import com.github.eikek.calev._ - -object CalevFs2 { - - def parse[F[_]](str: String)(implicit E: ApplicativeError[F, Throwable]): F[CalEvent] = - CalEvent - .parse(str) - .fold( - err => E.raiseError(new Exception(err)), - ce => ce.pure[F] - ) - - def parseStream[F[_]]( - str: String - )(implicit E: ApplicativeError[F, Throwable]): Stream[F, CalEvent] = - Stream.eval(parse[F](str)) - - def nextElapses[F[_]](ref: ZonedDateTime)(ce: CalEvent): Stream[F, ZonedDateTime] = - Stream - .emit(ce.nextElapse(ref)) - .unNoneTerminate - .flatMap(date => Stream.emit(date) ++ nextElapses(date)(ce)) - - def evalNow[F[_]: Sync]: Stream[F, ZonedDateTime] = - Stream.eval(Sync[F].delay(ZonedDateTime.now)) - - def durationFromNow[F[_]: Sync](ce: CalEvent): Stream[F, FiniteDuration] = - evalNow[F] - .map(now => - ce.nextElapse(now) - .map { end => - val millis = now.until(end, ChronoUnit.MILLIS) - FiniteDuration(millis, TimeUnit.MILLISECONDS) - } - ) - .unNoneTerminate - - def sleep[F[_]: Sync](ce: CalEvent)(implicit T: Timer[F]): Stream[F, Unit] = - durationFromNow[F](ce).flatMap(Stream.sleep[F]) - - def awakeEvery[F[_]: Sync](ce: CalEvent)(implicit T: Timer[F]): Stream[F, Unit] = - sleep(ce).repeat - - def schedule[F[_]: Concurrent, A](tasks: List[(CalEvent, Stream[F, A])])(implicit - timer: Timer[F] - ): Stream[F, A] = { - val scheduled = tasks.map { case (ce, task) => awakeEvery[F](ce) >> task } - Stream.emits(scheduled).covary[F].parJoinUnbounded - } -} diff --git a/modules/fs2/src/test/scala/com/github/eikek/fs2calev/CalevFs2Test.scala b/modules/fs2/src/test/scala/com/github/eikek/fs2calev/CalevFs2Test.scala deleted file mode 100644 index fafc86d..0000000 --- a/modules/fs2/src/test/scala/com/github/eikek/fs2calev/CalevFs2Test.scala +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.eikek.fs2calev - -import java.time._ - -import scala.concurrent.ExecutionContext - -import cats.effect._ -import com.github.eikek.calev._ -import fs2.Stream -import munit._ - -class CalevFs2Test extends FunSuite { - - implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global) - private val evalInstantNow = Stream.eval(IO(Instant.now())) - - test("awake") { - val evenSeconds = CalEvent.unsafe("*-*-* *:*:0/2") - val io = CalevFs2.awakeEvery[IO](evenSeconds) >> evalInstantNow - val times = io.map(_.getEpochSecond).take(2).forall(_ % 2 == 0).compile.last - assertEquals(times.unsafeRunSync(), Option(true)) - } - - test("nextElapses") { - val ref = zdt(2020, 3, 6, 1, 47, 12).withZoneSameLocal(CalEvent.UTC) - val ce = CalEvent.unsafe("*-*-* 0/2:0:0") - val next = CalevFs2.nextElapses[IO](ref)(ce).take(3).compile.toVector.unsafeRunSync() - assertEquals( - next, - Vector(zdt(2020, 3, 6, 2, 0, 0), zdt(2020, 3, 6, 4, 0, 0), zdt(2020, 3, 6, 6, 0, 0)) - ) - } - - private def zdt(y: Int, month: Int, d: Int, h: Int, min: Int, sec: Int): ZonedDateTime = - ZonedDateTime.of(LocalDate.of(y, month, d), LocalTime.of(h, min, sec), CalEvent.UTC) -} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 86c47fd..b43d653 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,7 +5,7 @@ object Dependencies { val akkaVersion = "2.6.16" val circeVersion = "0.14.1" val doobieVersion = "0.13.4" - val fs2Version = "2.5.9" + val fs2Version = "3.1.0" val h2Version = "1.4.200" val jacksonVersion = "2.12.4" val log4sVersion = "1.8.2"