Last active
December 8, 2020 16:28
-
-
Save jdegoes/c751b21bd789ebb1b37d9c2af2f93d00 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package zio.cron | |
import java.time.{ Month => JMonth } | |
import java.time.{ DayOfWeek => JDayOfWeek } | |
import zio.Schedule | |
// 0 16 1,15 * * echo Timesheets Due > /dev/console | |
final case class CronSchedule( | |
minute: CronSchedule.Minute, | |
hour: CronSchedule.Hour, | |
dayOfMonth: CronSchedule.DayOfMonth, | |
month: CronSchedule.Month, | |
dayOfWeek: CronSchedule.DayOfWeek | |
) { | |
final def render: String = | |
s"${minute.render} ${hour.render} ${dayOfMonth.render} ${month.render} ${dayOfWeek.render}" | |
final def toSchedule: Schedule[Any, Any, Long] = | |
minute.toSchedule *> hour.toSchedule *> dayOfMonth.toSchedule *> month.toSchedule *> dayOfWeek.toSchedule | |
} | |
object CronSchedule { | |
sealed trait Minute { self => | |
import Minute._ | |
override final def equals(that: scala.Any): Boolean = | |
that match { | |
case that: Minute => self.toList.toSet == that.toList.toSet | |
case _ => false | |
} | |
final def filter(f: Int => Boolean): Minute = List(toList.filter(f)) | |
final def filterNot(f: Int => Boolean): Minute = filter(v => !f(v)) | |
final def render: String = | |
self match { | |
case Any => "*" | |
case List(value) => value.mkString(",") | |
case Range(value) => | |
if (value.step != 1) s"${value.start}-${value.end}/${value.step}" | |
else s"${value.start}-${value.end}" | |
} | |
final def simplify: Minute = { | |
lazy val elements = self.toList | |
if (self == Any || (Any.toList.toSet -- elements.toSet).isEmpty) Any | |
else if (elements.isEmpty) self | |
else { | |
val min = elements.min | |
val max = elements.max | |
val range = min to max | |
if ((range.toSet -- elements.toSet).isEmpty) Range(min to max) | |
else self | |
} | |
} | |
final def toList: scala.List[Int] = | |
self match { | |
case Any => (0 to 59).toList | |
case List(value) => value.filter(i => i >= 0 && i <= 59).toList.sorted | |
case Range(value) => value.filter(i => i >= 0 && i <= 59).toList.sorted | |
} | |
final def toSchedule: Schedule[Any, Any, Long] = | |
(toList.foldLeft[Schedule[Any, Any, Any]](Schedule.recurs(0)) { | |
case (acc, minute) => acc || Schedule.minuteOfHour(minute) | |
}) *> Schedule.count | |
} | |
object Minute { | |
object Any extends Minute | |
final case class List(value: Iterable[Int]) extends Minute | |
final case class Range(value: scala.Range) extends Minute | |
def any: Any = Any | |
def list(value: Int, values: Int*): Minute = List(Vector(value) ++ values.toVector) | |
def list(values: Iterable[Int]): Minute = List(values) | |
def range(range: scala.Range): Minute = Range(range) | |
def step(increment: Int): Minute = Range((0 to 59) by increment) | |
} | |
sealed trait Hour { self => | |
import Hour._ | |
final def filter(f: Int => Boolean): Hour = List(toList.filter(f)) | |
final def filterNot(f: Int => Boolean): Hour = filter(v => !f(v)) | |
final def render: String = | |
self match { | |
case Any => "*" | |
case List(value) => value.mkString(",") | |
case Range(value) => | |
if (value.step != 1) s"${value.start}-${value.end}/${value.step}" | |
else s"${value.start}-${value.end}" | |
} | |
final def simplify: Hour = { | |
lazy val elements = self.toList | |
if (self == Any || (Any.toList.toSet -- elements.toSet).isEmpty) Any | |
else if (elements.isEmpty) self | |
else { | |
val min = elements.min | |
val max = elements.max | |
val range = min to max | |
if ((range.toSet -- elements.toSet).isEmpty) Range(min to max) | |
else self | |
} | |
} | |
final def toList: scala.List[Int] = | |
self match { | |
case Any => (0 to 23).toList | |
case List(value) => value.filter(i => i >= 0 && i <= 23).toList.sorted | |
case Range(value) => value.filter(i => i >= 0 && i <= 23).toList.sorted | |
} | |
final def toSchedule: Schedule[Any, Any, Long] = | |
(toList.foldLeft[Schedule[Any, Any, Any]](Schedule.recurs(0)) { | |
case (acc, hour) => acc || Schedule.hourOfDay(hour) | |
}) *> Schedule.count | |
} | |
object Hour { | |
object Any extends Hour | |
final case class List(value: Iterable[Int]) extends Hour | |
final case class Range(value: scala.Range) extends Hour | |
} | |
sealed trait DayOfMonth { self => | |
import DayOfMonth._ | |
final def filter(f: Int => Boolean): DayOfMonth = List(toList.filter(f)) | |
final def filterNot(f: Int => Boolean): DayOfMonth = filter(v => !f(v)) | |
final def render: String = | |
self match { | |
case Any => "*" | |
case List(value) => value.mkString(",") | |
case Range(value) => | |
if (value.step != 1) s"${value.start}-${value.end}/${value.step}" | |
else s"${value.start}-${value.end}" | |
} | |
final def simplify: DayOfMonth = { | |
lazy val elements = self.toList | |
if (self == Any || (Any.toList.toSet -- elements.toSet).isEmpty) Any | |
else if (elements.isEmpty) self | |
else { | |
val min = elements.min | |
val max = elements.max | |
val range = min to max | |
if ((range.toSet -- elements.toSet).isEmpty) Range(min to max) | |
else self | |
} | |
} | |
final def toList: scala.List[Int] = | |
self match { | |
case Any => (1 to 31).toList | |
case List(value) => value.filter(i => i >= 1 && i <= 31).toList.sorted | |
case Range(value) => value.filter(i => i >= 1 && i <= 31).toList.sorted | |
} | |
final def toSchedule: Schedule[Any, Any, Long] = | |
(toList.foldLeft[Schedule[Any, Any, Any]](Schedule.recurs(0)) { | |
case (acc, day) => acc || Schedule.dayOfMonth(day) | |
}) *> Schedule.count | |
} | |
object DayOfMonth { | |
object Any extends DayOfMonth | |
final case class List(value: Iterable[Int]) extends DayOfMonth | |
final case class Range(value: scala.Range) extends DayOfMonth | |
} | |
sealed trait Month { self => | |
import Month._ | |
final def filter(f: JMonth => Boolean): Month = List(toList.filter(f)) | |
final def filterNot(f: JMonth => Boolean): Month = filter(v => !f(v)) | |
final def render: String = | |
self match { | |
case Any => "*" | |
case List(value) => value.mkString(",") | |
case Range(start, end, step) => | |
if (step != 1) s"${start}-${end}/${step}" | |
else s"${start}-${end}" | |
} | |
final def simplify: Month = { | |
lazy val elements = self.toList | |
if (self == Any || (Any.toList.toSet -- elements.toSet).isEmpty) Any | |
else if (elements.isEmpty) self | |
else { | |
val min = elements.map(_.getValue()).min | |
val max = elements.map(_.getValue()).max | |
val range = (min to max).map(JMonth.of(_)) | |
if ((range.toSet -- elements.toSet).isEmpty) Range(JMonth.of(min), JMonth.of(max)) | |
else self | |
} | |
} | |
final def toList: scala.List[JMonth] = | |
self match { | |
case Any => JMonth.values.toList | |
case List(value) => value.toList.sortBy(_.getValue()) | |
case Range(start, end, increment) => | |
scala.Range(start.getValue(), end.getValue(), increment).toList.map(JMonth.of(_)) | |
} | |
final def toSchedule: Schedule[Any, Any, Long] = | |
(toList.foldLeft[Schedule[Any, Any, Any]](Schedule.recurs(0)) { | |
case (acc, month) => acc || ??? // FIXME: Add Schedule.monthOfYear | |
}) *> Schedule.count | |
} | |
object Month { | |
object Any extends Month | |
final case class List(value: Iterable[JMonth]) extends Month | |
final case class Range(start: JMonth, end: JMonth, increment: Int = 1) extends Month | |
} | |
sealed trait DayOfWeek { self => | |
import DayOfWeek._ | |
final def filter(f: JDayOfWeek => Boolean): DayOfWeek = List(toList.filter(f)) | |
final def filterNot(f: JDayOfWeek => Boolean): DayOfWeek = filter(v => !f(v)) | |
final def render: String = | |
self match { | |
case Any => "*" | |
case List(value) => value.mkString(",") | |
case Range(start, end, step) => | |
if (step != 1) s"${start}-${end}/${step}" | |
else s"${start}-${end}" | |
} | |
final def toList: scala.List[JDayOfWeek] = | |
self match { | |
case Any => JDayOfWeek.values().toList | |
case List(value) => value.toList.sortBy(_.getValue()) | |
case Range(start, end, increment) => | |
scala.Range(start.getValue(), end.getValue(), increment).map(JDayOfWeek.of(_)).toList | |
} | |
final def toSchedule: Schedule[Any, Any, Long] = | |
(toList.foldLeft[Schedule[Any, Any, Any]](Schedule.recurs(0)) { | |
case (acc, dayOfWeek) => acc || Schedule.dayOfWeek(dayOfWeek.getValue()) | |
}) *> Schedule.count | |
} | |
object DayOfWeek { | |
object Any extends DayOfWeek | |
final case class List(value: Iterable[JDayOfWeek]) extends DayOfWeek | |
final case class Range(start: JDayOfWeek, end: JDayOfWeek, increment: Int = 1) extends DayOfWeek | |
} | |
// 0 16 1,15 * * | |
def fromString(string: String): Either[String, CronSchedule] = | |
??? | |
} | |
final case class CrontabEntry(schedule: CronSchedule, process: String) { | |
def render: String = schedule.render + " " + process | |
} | |
object CrontabEntry { | |
// 0 16 1,15 * * echo Timesheets Due > /dev/console | |
def fromString(string: String): Either[String, CrontabEntry] = | |
??? | |
} | |
final case class CrontabFile(entries: Vector[CrontabEntry]) { | |
def render: String = entries.map(_.render).mkString("\n") | |
} | |
object CrontabFile { | |
def fromString(string: String): Either[::[String], CrontabFile] = { | |
val list: List[Either[String, CrontabEntry]] = | |
string.split("""(\r?\n)+""").toList.map(_.trim).filterNot(_ == "").map(CrontabEntry.fromString(_)) | |
def collect[A, B](value: List[Either[A, B]]): Either[::[A], List[B]] = | |
value match { | |
case Nil => Right(Nil) | |
case head :: tail => | |
val tail2 = collect(tail) | |
tail2 match { | |
case Left(as) => head.left.toOption.fold(Left(as))(a => Left(::(a, as))) | |
case Right(bs) => head.fold[Either[::[A], List[B]]](a => Left(::(a, Nil)), b => Right(b :: bs)) | |
} | |
} | |
collect(list).map(list => CrontabFile(list.toVector)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment