From e624a7a1b555efeca829c7da95ed4419fcb9d5f3 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Wed, 20 Nov 2024 09:36:29 +0100 Subject: [PATCH 01/12] refactor: define a Parser interface and use it for `Cron` object --- .../shared/src/main/scala/cron4s/Cron.scala | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/modules/core/shared/src/main/scala/cron4s/Cron.scala b/modules/core/shared/src/main/scala/cron4s/Cron.scala index b1d70e5a..d7c9617a 100644 --- a/modules/core/shared/src/main/scala/cron4s/Cron.scala +++ b/modules/core/shared/src/main/scala/cron4s/Cron.scala @@ -19,13 +19,12 @@ package cron4s import scala.scalajs.js.annotation.JSExportTopLevel import scala.util.Try -/** - * The entry point for parsing cron expressions - * - * @author Antonio Alonso Dominguez - */ -@JSExportTopLevel("Cron") -object Cron { +@FunctionalInterface +trait Parser { + def parse(e: String): Either[Error, CronExpr] +} + +class Cron(parser: Parser) { /** * Parses the given cron expression into a cron AST using Either as return type. This is a short-hand for @@ -47,7 +46,7 @@ object Cron { */ @inline def parse(e: String): Either[Error, CronExpr] = - parsing.parse(e).flatMap(validation.validateCron) + parser.parse(e).flatMap(validation.validateCron) /** * Parses the given cron expression into a cron AST using Try as return type @@ -75,3 +74,11 @@ object Cron { case Right(expr) => expr } } + +/** + * The entry point for parsing cron expressions + * + * @author Antonio Alonso Dominguez + */ +@JSExportTopLevel("Cron") +object Cron extends Cron(parsing.parse) From a03c5c09b038bc154e2f28ffce21272bcff51bf0 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Wed, 20 Nov 2024 10:11:40 +0100 Subject: [PATCH 02/12] refactor: move parser-combinators parser implementation into its own module --- .../src/main/scala/cron4s/atto/package.scala | 61 +++++++++--------- build.sbt | 64 +++++++++++++++---- .../shared/src/main/scala/cron4s/Cron.scala | 11 +--- .../shared/src/main/scala/cron4s/Cron.scala | 27 ++++++++ .../src/main/scala/cron4s/parsing/base.scala | 0 .../src/main/scala/cron4s/parsing/lexer.scala | 0 .../main/scala/cron4s/parsing/package.scala | 0 .../main/scala/cron4s/parsing/parser.scala | 0 project/Dependencies.scala | 9 ++- 9 files changed, 118 insertions(+), 54 deletions(-) create mode 100644 modules/parserc/shared/src/main/scala/cron4s/Cron.scala rename modules/{core => parserc}/shared/src/main/scala/cron4s/parsing/base.scala (100%) rename modules/{core => parserc}/shared/src/main/scala/cron4s/parsing/lexer.scala (100%) rename modules/{core => parserc}/shared/src/main/scala/cron4s/parsing/package.scala (100%) rename modules/{core => parserc}/shared/src/main/scala/cron4s/parsing/parser.scala (100%) diff --git a/bench/src/main/scala/cron4s/atto/package.scala b/bench/src/main/scala/cron4s/atto/package.scala index ec2f8094..77e6aea3 100644 --- a/bench/src/main/scala/cron4s/atto/package.scala +++ b/bench/src/main/scala/cron4s/atto/package.scala @@ -16,6 +16,7 @@ package cron4s +import _root_.atto.{Parser => AttoParser} import _root_.atto._ import Atto._ import cats.implicits._ @@ -25,7 +26,7 @@ package object atto { import CronField._ import CronUnit._ - private def oneOrTwoDigitsPositiveInt: Parser[Int] = { + private def oneOrTwoDigitsPositiveInt: AttoParser[Int] = { val getDigits = for { d1 <- digit @@ -43,16 +44,16 @@ package object atto { ) } namedOpaque "oneOrTwoDigitsPositiveInt" - private val sexagesimal: Parser[Int] = oneOrTwoDigitsPositiveInt.filter(x => x >= 0 && x < 60) + private val sexagesimal: AttoParser[Int] = oneOrTwoDigitsPositiveInt.filter(x => x >= 0 && x < 60) - private val literal: Parser[String] = takeWhile1(x => x != ' ' && x != '-') + private val literal: AttoParser[String] = takeWhile1(x => x != ' ' && x != '-') - private val hyphen: Parser[Char] = elem(_ == '-', "hyphen") - private val comma: Parser[Char] = elem(_ == ',', "comma") - private val slash: Parser[Char] = elem(_ == '/', "slash") - private val asterisk: Parser[Char] = elem(_ == '*', "asterisk") - private val questionMark: Parser[Char] = elem(_ == '?', "question-mark") - private val blank: Parser[Char] = elem(_ == ' ', "blank") + private val hyphen: AttoParser[Char] = elem(_ == '-', "hyphen") + private val comma: AttoParser[Char] = elem(_ == ',', "comma") + private val slash: AttoParser[Char] = elem(_ == '/', "slash") + private val asterisk: AttoParser[Char] = elem(_ == '*', "asterisk") + private val questionMark: AttoParser[Char] = elem(_ == '?', "question-mark") + private val blank: AttoParser[Char] = elem(_ == ' ', "blank") // ---------------------------------------- // Individual Expression Atoms @@ -60,22 +61,22 @@ package object atto { // Seconds - val seconds: Parser[ConstNode[Second]] = + val seconds: AttoParser[ConstNode[Second]] = sexagesimal.map(ConstNode[Second](_)) // Minutes - val minutes: Parser[ConstNode[Minute]] = + val minutes: AttoParser[ConstNode[Minute]] = sexagesimal.map(ConstNode[Minute](_)) // Hours - val hours: Parser[ConstNode[Hour]] = + val hours: AttoParser[ConstNode[Hour]] = oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x < 24)).map(ConstNode[Hour](_)) // Days Of Month - val daysOfMonth: Parser[ConstNode[DayOfMonth]] = + val daysOfMonth: AttoParser[ConstNode[DayOfMonth]] = oneOrTwoDigitsPositiveInt.filter(x => (x >= 1) && (x <= 31)).map(ConstNode[DayOfMonth](_)) // Months @@ -89,7 +90,7 @@ package object atto { ConstNode[Month](index + 1, Some(value)) } - val months: Parser[ConstNode[Month]] = + val months: AttoParser[ConstNode[Month]] = textualMonths | numericMonths // Days Of Week @@ -103,31 +104,31 @@ package object atto { ConstNode[DayOfWeek](index, Some(value)) } - val daysOfWeek: Parser[ConstNode[DayOfWeek]] = + val daysOfWeek: AttoParser[ConstNode[DayOfWeek]] = textualDaysOfWeek | numericDaysOfWeek // ---------------------------------------- // Field-Based Expression Atoms // ---------------------------------------- - def each[F <: CronField](implicit unit: CronUnit[F]): Parser[EachNode[F]] = + def each[F <: CronField](implicit unit: CronUnit[F]): AttoParser[EachNode[F]] = asterisk.as(EachNode[F]) - def any[F <: CronField](implicit unit: CronUnit[F]): Parser[AnyNode[F]] = + def any[F <: CronField](implicit unit: CronUnit[F]): AttoParser[AnyNode[F]] = questionMark.as(AnyNode[F]) - def between[F <: CronField](base: Parser[ConstNode[F]])(implicit + def between[F <: CronField](base: AttoParser[ConstNode[F]])(implicit unit: CronUnit[F] - ): Parser[BetweenNode[F]] = + ): AttoParser[BetweenNode[F]] = for { min <- base <~ hyphen max <- base } yield BetweenNode[F](min, max) - def several[F <: CronField](base: Parser[ConstNode[F]])(implicit + def several[F <: CronField](base: AttoParser[ConstNode[F]])(implicit unit: CronUnit[F] - ): Parser[SeveralNode[F]] = { - def compose(b: => Parser[EnumerableNode[F]]) = + ): AttoParser[SeveralNode[F]] = { + def compose(b: => AttoParser[EnumerableNode[F]]) = sepBy(b, comma) .collect { case first :: second :: tail => SeveralNode(first, second, tail: _*) @@ -136,10 +137,10 @@ package object atto { compose(between(base).map(between2Enumerable) | base.map(const2Enumerable)) } - def every[F <: CronField](base: Parser[ConstNode[F]])(implicit + def every[F <: CronField](base: AttoParser[ConstNode[F]])(implicit unit: CronUnit[F] - ): Parser[EveryNode[F]] = { - def compose(b: => Parser[DivisibleNode[F]]) = + ): AttoParser[EveryNode[F]] = { + def compose(b: => AttoParser[DivisibleNode[F]]) = ((b <~ slash) ~ oneOrTwoDigitsPositiveInt.filter(_ > 0)).map { case (exp, freq) => EveryNode[F](exp, freq) } @@ -155,18 +156,18 @@ package object atto { // AST Parsing & Building // ---------------------------------------- - def field[F <: CronField](base: Parser[ConstNode[F]])(implicit + def field[F <: CronField](base: AttoParser[ConstNode[F]])(implicit unit: CronUnit[F] - ): Parser[FieldNode[F]] = + ): AttoParser[FieldNode[F]] = every(base).map(every2Field) | several(base).map(several2Field) | between(base).map(between2Field) | base.map(const2Field) | each[F].map(each2Field) - def fieldWithAny[F <: CronField](base: Parser[ConstNode[F]])(implicit + def fieldWithAny[F <: CronField](base: AttoParser[ConstNode[F]])(implicit unit: CronUnit[F] - ): Parser[FieldNodeWithAny[F]] = + ): AttoParser[FieldNodeWithAny[F]] = every(base).map(every2FieldWithAny) | several(base).map(several2FieldWithAny) | between(base).map(between2FieldWithAny) | @@ -174,7 +175,7 @@ package object atto { each[F].map(each2FieldWithAny) | any[F].map(any2FieldWithAny) - val cron: Parser[CronExpr] = for { + val cron: AttoParser[CronExpr] = for { sec <- field(seconds) <~ blank min <- field(minutes) <~ blank hour <- field(hours) <~ blank diff --git a/build.sbt b/build.sbt index 73c7934f..cc008823 100644 --- a/build.sbt +++ b/build.sbt @@ -279,8 +279,8 @@ lazy val cron4sJS = (project in file(".js")) .settings(commonJsSettings: _*) .settings(noPublishSettings) .enablePlugins(ScalaJSPlugin) - .aggregate(core.js, momentjs, circe.js, decline.js, testkit.js, tests.js) - .dependsOn(core.js, momentjs, circe.js, decline.js, testkit.js, tests.js % Test) + .aggregate(core.js, parserc.js, momentjs, circe.js, decline.js, testkit.js, tests.js) + .dependsOn(core.js, parserc.js, momentjs, circe.js, decline.js, testkit.js, tests.js % Test) lazy val cron4sJVM = (project in file(".jvm")) .settings( @@ -291,8 +291,17 @@ lazy val cron4sJVM = (project in file(".jvm")) .settings(commonJvmSettings) .settings(consoleSettings) .settings(noPublishSettings) - .aggregate(core.jvm, joda, doobie, circe.jvm, decline.jvm, testkit.jvm, tests.jvm) - .dependsOn(core.jvm, joda, doobie, circe.jvm, decline.jvm, testkit.jvm, tests.jvm % Test) + .aggregate(core.jvm, parserc.jvm, joda, doobie, circe.jvm, decline.jvm, testkit.jvm, tests.jvm) + .dependsOn( + core.jvm, + parserc.jvm, + joda, + doobie, + circe.jvm, + decline.jvm, + testkit.jvm, + tests.jvm % Test + ) lazy val cron4sNative = (project in file(".native")) .settings( @@ -301,8 +310,22 @@ lazy val cron4sNative = (project in file(".native")) ) .settings(commonSettings) .settings(noPublishSettings) - .aggregate(core.native, circe.native, decline.native, testkit.native, tests.native) - .dependsOn(core.native, circe.native, decline.native, testkit.native, tests.native % Test) + .aggregate( + core.native, + parserc.native, + circe.native, + decline.native, + testkit.native, + tests.native + ) + .dependsOn( + core.native, + parserc.native, + circe.native, + decline.native, + testkit.native, + tests.native % Test + ) lazy val docs = project .enablePlugins(MicrositesPlugin, ScalaUnidocPlugin, GhpagesPlugin) @@ -312,7 +335,7 @@ lazy val docs = project .settings(commonSettings) .settings(noPublishSettings) .settings(docSettings) - .dependsOn(core.jvm, joda, doobie, circe.jvm, decline.jvm, testkit.jvm) + .dependsOn(core.jvm, parserc.jvm, joda, doobie, circe.jvm, decline.jvm, testkit.jvm) // ================================================================================= // Main modules @@ -335,6 +358,23 @@ lazy val core = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file(" .jvmSettings(mimaSettings("core")) .nativeSettings(Dependencies.coreNative) +lazy val parserc = + (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/parserc")) + .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) + .settings( + name := "parserc", + moduleName := "cron4s-parserc" + ) + .settings(commonSettings) + .settings(publishSettings) + .settings(Dependencies.parserc) + .jsSettings(commonJsSettings) + .jsSettings(Dependencies.coreJS) + .jvmSettings(commonJvmSettings) + .jvmSettings(Dependencies.coreJVM) + .nativeSettings(Dependencies.coreNative) + .dependsOn(core) + lazy val testkit = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/testkit")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) @@ -351,7 +391,7 @@ lazy val testkit = .jvmSettings(Dependencies.coreJVM) .jvmSettings(mimaSettings("testkit")) .nativeSettings(Dependencies.coreNative) - .dependsOn(core) + .dependsOn(core, parserc) lazy val tests = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("tests")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin) @@ -378,7 +418,7 @@ lazy val bench = (project in file("bench")) .settings(commonJvmSettings) .settings(Dependencies.bench) .enablePlugins(JmhPlugin) - .dependsOn(core.jvm) + .dependsOn(core.jvm, parserc.jvm) // ================================================================================= // DateTime library extensions @@ -432,7 +472,7 @@ lazy val circe = .jvmSettings(Dependencies.coreJVM) .jsSettings(commonJsSettings) .nativeSettings(Dependencies.coreNative) - .dependsOn(core, testkit % Test) + .dependsOn(core, parserc, testkit % Test) lazy val decline = (crossProject(JSPlatform, JVMPlatform, NativePlatform).crossType(CrossType.Pure) in file( @@ -452,7 +492,7 @@ lazy val decline = .jvmSettings(Dependencies.coreJVM) .jsSettings(commonJsSettings) .nativeSettings(Dependencies.coreNative) - .dependsOn(core, testkit % Test) + .dependsOn(core, parserc, testkit % Test) lazy val doobie = (project in file("modules/doobie")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) @@ -467,7 +507,7 @@ lazy val doobie = (project in file("modules/doobie")) .settings(mimaSettings("doobie")) .settings(Dependencies.doobie) .settings(Dependencies.coreJVM) - .dependsOn(core.jvm, testkit.jvm % Test) + .dependsOn(core.jvm, parserc.jvm, testkit.jvm % Test) // ================================================================================= // Utility command aliases diff --git a/modules/core/shared/src/main/scala/cron4s/Cron.scala b/modules/core/shared/src/main/scala/cron4s/Cron.scala index d7c9617a..1b4a0659 100644 --- a/modules/core/shared/src/main/scala/cron4s/Cron.scala +++ b/modules/core/shared/src/main/scala/cron4s/Cron.scala @@ -16,7 +16,6 @@ package cron4s -import scala.scalajs.js.annotation.JSExportTopLevel import scala.util.Try @FunctionalInterface @@ -24,7 +23,7 @@ trait Parser { def parse(e: String): Either[Error, CronExpr] } -class Cron(parser: Parser) { +private[cron4s] class CronImpl(parser: Parser) { /** * Parses the given cron expression into a cron AST using Either as return type. This is a short-hand for @@ -74,11 +73,3 @@ class Cron(parser: Parser) { case Right(expr) => expr } } - -/** - * The entry point for parsing cron expressions - * - * @author Antonio Alonso Dominguez - */ -@JSExportTopLevel("Cron") -object Cron extends Cron(parsing.parse) diff --git a/modules/parserc/shared/src/main/scala/cron4s/Cron.scala b/modules/parserc/shared/src/main/scala/cron4s/Cron.scala new file mode 100644 index 00000000..2965cf1c --- /dev/null +++ b/modules/parserc/shared/src/main/scala/cron4s/Cron.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import scala.scalajs.js.annotation.JSExportTopLevel + +/** + * The entry point for parsing cron expressions + * + * @author Antonio Alonso Dominguez + */ +@JSExportTopLevel("Cron") +object Cron extends CronImpl(parsing.parse) diff --git a/modules/core/shared/src/main/scala/cron4s/parsing/base.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/base.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/parsing/base.scala rename to modules/parserc/shared/src/main/scala/cron4s/parsing/base.scala diff --git a/modules/core/shared/src/main/scala/cron4s/parsing/lexer.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/lexer.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/parsing/lexer.scala rename to modules/parserc/shared/src/main/scala/cron4s/parsing/lexer.scala diff --git a/modules/core/shared/src/main/scala/cron4s/parsing/package.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/parsing/package.scala rename to modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala diff --git a/modules/core/shared/src/main/scala/cron4s/parsing/parser.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/parser.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/parsing/parser.scala rename to modules/parserc/shared/src/main/scala/cron4s/parsing/parser.scala diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 62dec65c..e478c666 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -33,14 +33,19 @@ object Dependencies { lazy val core = Def.settings( libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-core" % version.cats.main, - "org.scala-lang.modules" %%% "scala-parser-combinators" % version.parserc + "org.typelevel" %%% "cats-core" % version.cats.main ), libraryDependencies ++= (if (scalaVersion.value.startsWith("2.")) Seq("com.chuusai" %%% "shapeless" % version.shapeless) else Seq.empty) ) + lazy val parserc = Def.settings { + libraryDependencies ++= Seq( + "org.scala-lang.modules" %%% "scala-parser-combinators" % version.parserc + ) + } + lazy val coreJS = Def.settings { libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % version.scalaJavaTime } From 5c923233500fe17d2a00afb29fa5ce41189577d5 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Wed, 20 Nov 2024 10:32:15 +0100 Subject: [PATCH 03/12] refactor: move atto parser implementation into its own module --- .../scala/cron4s/bench/ParserBenchmark.scala | 2 +- build.sbt | 19 +++++++++++++++++-- .../src/main/scala/cron4s/atto/Parser.scala | 14 +++++++------- project/Dependencies.scala | 12 ++++++------ 4 files changed, 31 insertions(+), 16 deletions(-) rename bench/src/main/scala/cron4s/atto/package.scala => modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala (97%) diff --git a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala index 07ebb428..fcf0084b 100644 --- a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala +++ b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala @@ -41,5 +41,5 @@ class ParserBenchmark { def parserCombinators() = parsing.parse(cronString) @Benchmark - def attoParser() = atto.parse(cronString) + def attoParser() = atto.Parser.parse(cronString) } diff --git a/build.sbt b/build.sbt index cc008823..b638c7db 100644 --- a/build.sbt +++ b/build.sbt @@ -375,6 +375,22 @@ lazy val parserc = .nativeSettings(Dependencies.coreNative) .dependsOn(core) +lazy val atto = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/atto")) + .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) + .settings( + name := "atto", + moduleName := "cron4s-atto" + ) + .settings(commonSettings) + .settings(publishSettings) + .settings(Dependencies.atto) + .jsSettings(commonJsSettings) + .jsSettings(Dependencies.coreJS) + .jvmSettings(commonJvmSettings) + .jvmSettings(Dependencies.coreJVM) + .nativeSettings(Dependencies.coreNative) + .dependsOn(core) + lazy val testkit = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/testkit")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) @@ -416,9 +432,8 @@ lazy val bench = (project in file("bench")) .settings(commonSettings) .settings(noPublishSettings) .settings(commonJvmSettings) - .settings(Dependencies.bench) .enablePlugins(JmhPlugin) - .dependsOn(core.jvm, parserc.jvm) + .dependsOn(core.jvm, atto.jvm, parserc.jvm) // ================================================================================= // DateTime library extensions diff --git a/bench/src/main/scala/cron4s/atto/package.scala b/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala similarity index 97% rename from bench/src/main/scala/cron4s/atto/package.scala rename to modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala index 77e6aea3..d0a02a57 100644 --- a/bench/src/main/scala/cron4s/atto/package.scala +++ b/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala @@ -14,17 +14,17 @@ * limitations under the License. */ -package cron4s +package cron4s.atto -import _root_.atto.{Parser => AttoParser} -import _root_.atto._ -import Atto._ +import _root_.atto.{Parser => AttoParser, _} +import atto.Atto._ import cats.implicits._ +import cron4s.CronField._ +import cron4s.CronUnit._ +import cron4s._ import cron4s.expr._ -package object atto { - import CronField._ - import CronUnit._ +object Parser extends cron4s.Parser { private def oneOrTwoDigitsPositiveInt: AttoParser[Int] = { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e478c666..ff428253 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -46,6 +46,12 @@ object Dependencies { ) } + lazy val atto = Def.settings { + libraryDependencies ++= Seq( + "org.tpolecat" %% "atto-core" % version.atto + ) + } + lazy val coreJS = Def.settings { libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % version.scalaJavaTime } @@ -75,12 +81,6 @@ object Dependencies { ) } - lazy val bench = Def.settings { - libraryDependencies ++= Seq( - "org.tpolecat" %% "atto-core" % version.atto - ) - } - // Dependencies of extension libraries lazy val joda = Def.settings { From bde8aa8f9554eda7e73d55440e3480d2dd8fb4ff Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Wed, 20 Nov 2024 11:13:03 +0100 Subject: [PATCH 04/12] feat: run parsing tests on both atto and parser-combinators implementations --- build.sbt | 2 +- .../src/test/scala/cron4s/CronSpec.scala | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index b638c7db..11afc3ea 100644 --- a/build.sbt +++ b/build.sbt @@ -407,7 +407,7 @@ lazy val testkit = .jvmSettings(Dependencies.coreJVM) .jvmSettings(mimaSettings("testkit")) .nativeSettings(Dependencies.coreNative) - .dependsOn(core, parserc) + .dependsOn(core, atto, parserc) lazy val tests = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("tests")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin) diff --git a/tests/shared/src/test/scala/cron4s/CronSpec.scala b/tests/shared/src/test/scala/cron4s/CronSpec.scala index 659d6c48..5045ef38 100644 --- a/tests/shared/src/test/scala/cron4s/CronSpec.scala +++ b/tests/shared/src/test/scala/cron4s/CronSpec.scala @@ -89,9 +89,12 @@ object CronSpec extends TableDrivenPropertyChecks { } -class CronSpec extends AnyFlatSpec with Matchers { +trait CronSpec extends Matchers { this: AnyFlatSpec => import CronSpec._ + def parser: cron4s.Parser + def cron: CronImpl = new CronImpl(parser) + "Cron" should "not parse an invalid expression" in { val _ = InvalidFieldCombination("Fields DayOfMonth and DayOfWeek can't both have the expression: *") @@ -112,10 +115,19 @@ class CronSpec extends AnyFlatSpec with Matchers { it should "parse a valid expression" in { val exprStr = ValidExpr.toString - Cron(exprStr) shouldBe Right(ValidExpr) + cron(exprStr) shouldBe Right(ValidExpr) - Cron.tryParse(exprStr) shouldBe Success(ValidExpr) + cron.tryParse(exprStr) shouldBe Success(ValidExpr) - Cron.unsafeParse(exprStr) shouldBe ValidExpr + cron.unsafeParse(exprStr) shouldBe ValidExpr } + +} + +class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + def parser = parsing.parse +} + +class AttoCronSpec extends AnyFlatSpec with CronSpec { + def parser = atto.Parser } From dbc5c1f2231f645dc7c757c7be4ba1091f0bfd56 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Wed, 20 Nov 2024 11:41:43 +0100 Subject: [PATCH 05/12] feat: implement comparison tests between both parsers --- .../src/test/scala/cron4s/CronSpec.scala | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/shared/src/test/scala/cron4s/CronSpec.scala b/tests/shared/src/test/scala/cron4s/CronSpec.scala index 5045ef38..25259774 100644 --- a/tests/shared/src/test/scala/cron4s/CronSpec.scala +++ b/tests/shared/src/test/scala/cron4s/CronSpec.scala @@ -87,6 +87,16 @@ object CronSpec extends TableDrivenPropertyChecks { AnyNode[DayOfWeek] ) + val validExpressions = Table( + "expression", + "* 5 4 * * *", + "* 0 0,12 1 */2 *", + "* 5 4 * * sun", + "* 0 0,12 1 */2 *", + "0 0,5,10,15,20,25,30,35,40,45,50,55 * * * *", + "0 1 2-4 * 4,5,6 */3", + "1 5 4 * * mon-2,sun" + ) } trait CronSpec extends Matchers { this: AnyFlatSpec => @@ -131,3 +141,13 @@ class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { class AttoCronSpec extends AnyFlatSpec with CronSpec { def parser = atto.Parser } + +class CronParserComparisonSpec extends AnyFlatSpec with Matchers { + import CronSpec._ + + "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { + forAll(CronSpec.validExpressions) { expr => + parsing.parse(expr) shouldBe atto.Parser.parse(expr) + } + } +} From 84acabd5ef28ee13c8c39acfcfb7e95331477f97 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Wed, 20 Nov 2024 18:23:13 +0100 Subject: [PATCH 06/12] refactor: Parser is now just a type describing a function, not an interface --- .../scala/cron4s/bench/ParserBenchmark.scala | 2 +- .../src/main/scala/cron4s/atto/Parser.scala | 2 +- .../src/main/scala/cron4s/atto/package.scala | 23 +++++++++++++++++++ .../cron4s/{Cron.scala => CronImpl.scala} | 7 +----- .../src/main/scala/cron4s/package.scala | 2 ++ .../src/test/scala/cron4s/CronSpec.scala | 4 ++-- 6 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 modules/atto/shared/src/main/scala/cron4s/atto/package.scala rename modules/core/shared/src/main/scala/cron4s/{Cron.scala => CronImpl.scala} (94%) diff --git a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala index fcf0084b..fe69846a 100644 --- a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala +++ b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala @@ -41,5 +41,5 @@ class ParserBenchmark { def parserCombinators() = parsing.parse(cronString) @Benchmark - def attoParser() = atto.Parser.parse(cronString) + def attoParser() = atto.parser(cronString) } diff --git a/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala b/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala index d0a02a57..fac57fd1 100644 --- a/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala +++ b/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala @@ -24,7 +24,7 @@ import cron4s.CronUnit._ import cron4s._ import cron4s.expr._ -object Parser extends cron4s.Parser { +private object Parser { private def oneOrTwoDigitsPositiveInt: AttoParser[Int] = { diff --git a/modules/atto/shared/src/main/scala/cron4s/atto/package.scala b/modules/atto/shared/src/main/scala/cron4s/atto/package.scala new file mode 100644 index 00000000..eb698830 --- /dev/null +++ b/modules/atto/shared/src/main/scala/cron4s/atto/package.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +package object atto { + + val parser: Parser = atto.Parser.parse + +} diff --git a/modules/core/shared/src/main/scala/cron4s/Cron.scala b/modules/core/shared/src/main/scala/cron4s/CronImpl.scala similarity index 94% rename from modules/core/shared/src/main/scala/cron4s/Cron.scala rename to modules/core/shared/src/main/scala/cron4s/CronImpl.scala index 1b4a0659..a352e536 100644 --- a/modules/core/shared/src/main/scala/cron4s/Cron.scala +++ b/modules/core/shared/src/main/scala/cron4s/CronImpl.scala @@ -18,11 +18,6 @@ package cron4s import scala.util.Try -@FunctionalInterface -trait Parser { - def parse(e: String): Either[Error, CronExpr] -} - private[cron4s] class CronImpl(parser: Parser) { /** @@ -45,7 +40,7 @@ private[cron4s] class CronImpl(parser: Parser) { */ @inline def parse(e: String): Either[Error, CronExpr] = - parser.parse(e).flatMap(validation.validateCron) + parser(e).flatMap(validation.validateCron) /** * Parses the given cron expression into a cron AST using Try as return type diff --git a/modules/core/shared/src/main/scala/cron4s/package.scala b/modules/core/shared/src/main/scala/cron4s/package.scala index e43f3083..98ba7b01 100644 --- a/modules/core/shared/src/main/scala/cron4s/package.scala +++ b/modules/core/shared/src/main/scala/cron4s/package.scala @@ -19,4 +19,6 @@ import cron4s.expr.NodeConversions package object cron4s extends AllSyntax with NodeConversions { type CronExpr = expr.CronExpr + + type Parser = String => Either[Error, CronExpr] } diff --git a/tests/shared/src/test/scala/cron4s/CronSpec.scala b/tests/shared/src/test/scala/cron4s/CronSpec.scala index 25259774..0465c0e1 100644 --- a/tests/shared/src/test/scala/cron4s/CronSpec.scala +++ b/tests/shared/src/test/scala/cron4s/CronSpec.scala @@ -139,7 +139,7 @@ class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { } class AttoCronSpec extends AnyFlatSpec with CronSpec { - def parser = atto.Parser + def parser = atto.parser } class CronParserComparisonSpec extends AnyFlatSpec with Matchers { @@ -147,7 +147,7 @@ class CronParserComparisonSpec extends AnyFlatSpec with Matchers { "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { forAll(CronSpec.validExpressions) { expr => - parsing.parse(expr) shouldBe atto.Parser.parse(expr) + parsing.parse(expr) shouldBe atto.parser(expr) } } } From 9b233a1beb348f51ccc49fbb0d8d7bf83fdd09f7 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Fri, 6 Dec 2024 15:05:01 +0100 Subject: [PATCH 07/12] feat: promote atto as the default parser it's still possible to use the parser-combinateor Cron parsing by depending on `cron4s-parserc` articfact an use `cron4s.parserc.Cron` --- build.sbt | 60 +++++++++++++++---- .../core/js/src/main/scala/cron4s/Cron.scala | 27 +++++++++ .../core/jvm/src/main/scala/cron4s/Cron.scala | 27 +++++++++ .../native}/src/main/scala/cron4s/Cron.scala | 0 .../scala/cron4s/lib/js/JsDateInstance.scala | 0 .../main/scala/cron4s/lib/js/package.scala | 0 .../shared/src/main/scala-2.12/compat.scala | 0 .../cron4s/datetime/PredicateReducer.scala | 0 .../scala-2/cron4s/datetime/Stepper.scala | 0 .../scala-2/cron4s/datetime/package.scala | 0 .../main/scala-2/cron4s/expr/CronExpr.scala | 0 .../scala-2/cron4s/expr/FieldSelector.scala | 0 .../scala-2/cron4s/expr/NodeConversions.scala | 0 .../cron4s/expr/WrapperInstances.scala | 0 .../src/main/scala-2/cron4s/expr/ops.scala | 0 .../main/scala-2/cron4s/expr/package.scala | 0 .../src/main/scala-2/cron4s/expr/parts.scala | 0 .../main/scala-2/cron4s/expr/wrappers.scala | 0 .../cron4s/validation/NodeValidator.scala | 0 .../main/scala-2/cron4s/validation/ops.scala | 0 .../scala-2/cron4s/validation/package.scala | 0 .../cron4s/datetime/PredicateReducer.scala | 0 .../scala-3/cron4s/datetime/Stepper.scala | 0 .../scala-3/cron4s/datetime/package.scala | 0 .../main/scala-3/cron4s/expr/CronExpr.scala | 0 .../scala-3/cron4s/expr/FieldSelector.scala | 0 .../scala-3/cron4s/expr/NodeConversions.scala | 0 .../cron4s/expr/WrapperInstances.scala | 0 .../src/main/scala-3/cron4s/expr/ops.scala | 0 .../main/scala-3/cron4s/expr/package.scala | 0 .../src/main/scala-3/cron4s/expr/parts.scala | 0 .../main/scala-3/cron4s/expr/wrappers.scala | 0 .../cron4s/validation/NodeValidators.scala | 0 .../main/scala-3/cron4s/validation/ops.scala | 0 .../scala-3/cron4s/validation/package.scala | 0 .../src/main/scala/cron4s/CronField.scala | 0 .../src/main/scala/cron4s/CronImpl.scala | 0 .../src/main/scala/cron4s/CronUnit.scala | 0 .../main/scala/cron4s/base/Enumerated.scala | 0 .../main/scala/cron4s/base/Predicate.scala | 0 .../scala/cron4s/datetime/DateTimeCron.scala | 0 .../scala/cron4s/datetime/DateTimeNode.scala | 0 .../scala/cron4s/datetime/DateTimeUnit.scala | 0 .../scala/cron4s/datetime/IsDateTime.scala | 0 .../main/scala/cron4s/datetime/errors.scala | 0 .../shared/src/main/scala/cron4s/errors.scala | 0 .../scala/cron4s/expr/CronExprInstances.scala | 0 .../cron4s/expr/DateCronExprInstances.scala | 0 .../main/scala/cron4s/expr/FieldExpr.scala | 0 .../cron4s/expr/TimeCronExprInstances.scala | 0 .../src/main/scala/cron4s/expr/nodes.scala | 0 .../lib/javatime/JavaTemporalInstance.scala | 0 .../scala/cron4s/lib/javatime/package.scala | 0 .../src/main/scala/cron4s/package.scala | 0 .../src/main/scala/cron4s/syntax/all.scala | 0 .../src/main/scala/cron4s/syntax/cron.scala | 0 .../main/scala/cron4s/syntax/enumerated.scala | 0 .../src/main/scala/cron4s/syntax/field.scala | 0 .../src/main/scala/cron4s/syntax/node.scala | 0 .../main/scala/cron4s/syntax/predicate.scala | 0 .../src/main/scala/cron4s/parserc/Cron.scala | 29 +++++++++ project/Dependencies.scala | 4 +- .../js/src/test/scala/cron4s/JsCronSpec.scala | 38 ++++++++++++ .../src/test/scala/cron4s/JvmCronSpec.scala | 38 ++++++++++++ .../test/scala/cron4s/NativeCronSpec.scala | 23 +++++++ .../src/test/scala/cron4s/CronSpec.scala | 18 ------ 66 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 modules/core/js/src/main/scala/cron4s/Cron.scala create mode 100644 modules/core/jvm/src/main/scala/cron4s/Cron.scala rename modules/{parserc/shared => core/native}/src/main/scala/cron4s/Cron.scala (100%) rename modules/{core => kernel}/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala (100%) rename modules/{core => kernel}/js/src/main/scala/cron4s/lib/js/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2.12/compat.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/datetime/Stepper.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/datetime/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/CronExpr.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/ops.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/parts.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/expr/wrappers.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/validation/ops.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-2/cron4s/validation/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/datetime/Stepper.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/datetime/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/CronExpr.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/ops.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/parts.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/expr/wrappers.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/validation/ops.scala (100%) rename modules/{core => kernel}/shared/src/main/scala-3/cron4s/validation/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/CronField.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/CronImpl.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/CronUnit.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/base/Enumerated.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/base/Predicate.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/datetime/IsDateTime.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/datetime/errors.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/errors.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/expr/CronExprInstances.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/expr/FieldExpr.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/expr/nodes.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/lib/javatime/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/package.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/syntax/all.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/syntax/cron.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/syntax/enumerated.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/syntax/field.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/syntax/node.scala (100%) rename modules/{core => kernel}/shared/src/main/scala/cron4s/syntax/predicate.scala (100%) create mode 100644 modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala create mode 100644 tests/js/src/test/scala/cron4s/JsCronSpec.scala create mode 100644 tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala create mode 100644 tests/native/src/test/scala/cron4s/NativeCronSpec.scala diff --git a/build.sbt b/build.sbt index 11afc3ea..735066bf 100644 --- a/build.sbt +++ b/build.sbt @@ -279,8 +279,17 @@ lazy val cron4sJS = (project in file(".js")) .settings(commonJsSettings: _*) .settings(noPublishSettings) .enablePlugins(ScalaJSPlugin) - .aggregate(core.js, parserc.js, momentjs, circe.js, decline.js, testkit.js, tests.js) - .dependsOn(core.js, parserc.js, momentjs, circe.js, decline.js, testkit.js, tests.js % Test) + .aggregate(core.js, kernel.js, atto.js, momentjs, circe.js, decline.js, testkit.js, tests.js) + .dependsOn( + core.js, + kernel.js, + atto.js, + momentjs, + circe.js, + decline.js, + testkit.js, + tests.js % Test + ) lazy val cron4sJVM = (project in file(".jvm")) .settings( @@ -291,10 +300,19 @@ lazy val cron4sJVM = (project in file(".jvm")) .settings(commonJvmSettings) .settings(consoleSettings) .settings(noPublishSettings) - .aggregate(core.jvm, parserc.jvm, joda, doobie, circe.jvm, decline.jvm, testkit.jvm, tests.jvm) + .aggregate( + kernel.jvm, + core.jvm, + atto.jvm, + joda, + doobie, + circe.jvm, + decline.jvm, + testkit.jvm, + tests.jvm + ) .dependsOn( core.jvm, - parserc.jvm, joda, doobie, circe.jvm, @@ -312,7 +330,6 @@ lazy val cron4sNative = (project in file(".native")) .settings(noPublishSettings) .aggregate( core.native, - parserc.native, circe.native, decline.native, testkit.native, @@ -320,7 +337,6 @@ lazy val cron4sNative = (project in file(".native")) ) .dependsOn( core.native, - parserc.native, circe.native, decline.native, testkit.native, @@ -341,6 +357,23 @@ lazy val docs = project // Main modules // ================================================================================= +lazy val kernel = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/kernel")) + .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) + .settings( + name := "kernel", + moduleName := "cron4s-kernel" + ) + .settings(commonSettings) + .settings(publishSettings) + .settings(Dependencies.core) + .jsSettings(commonJsSettings) + .jsSettings(Dependencies.coreJS) + .jvmSettings(commonJvmSettings) + .jvmSettings(consoleSettings) + .jvmSettings(Dependencies.coreJVM) + .jvmSettings(mimaSettings("kernel")) + .nativeSettings(Dependencies.coreNative) + lazy val core = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/core")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) .settings( @@ -352,11 +385,15 @@ lazy val core = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file(" .settings(Dependencies.core) .jsSettings(commonJsSettings) .jsSettings(Dependencies.coreJS) + .jsConfigure(_.dependsOn(atto.js)) .jvmSettings(commonJvmSettings) .jvmSettings(consoleSettings) .jvmSettings(Dependencies.coreJVM) .jvmSettings(mimaSettings("core")) + .jvmConfigure(_.dependsOn(atto.jvm)) .nativeSettings(Dependencies.coreNative) + .nativeConfigure(_.dependsOn(parserc.native)) + .dependsOn(kernel) lazy val parserc = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/parserc")) @@ -373,9 +410,9 @@ lazy val parserc = .jvmSettings(commonJvmSettings) .jvmSettings(Dependencies.coreJVM) .nativeSettings(Dependencies.coreNative) - .dependsOn(core) + .dependsOn(kernel) -lazy val atto = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/atto")) +lazy val atto = (crossProject(JSPlatform, JVMPlatform) in file("modules/atto")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) .settings( name := "atto", @@ -388,8 +425,7 @@ lazy val atto = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file(" .jsSettings(Dependencies.coreJS) .jvmSettings(commonJvmSettings) .jvmSettings(Dependencies.coreJVM) - .nativeSettings(Dependencies.coreNative) - .dependsOn(core) + .dependsOn(kernel) lazy val testkit = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/testkit")) @@ -407,7 +443,7 @@ lazy val testkit = .jvmSettings(Dependencies.coreJVM) .jvmSettings(mimaSettings("testkit")) .nativeSettings(Dependencies.coreNative) - .dependsOn(core, atto, parserc) + .dependsOn(core, parserc) lazy val tests = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("tests")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin) @@ -433,7 +469,7 @@ lazy val bench = (project in file("bench")) .settings(noPublishSettings) .settings(commonJvmSettings) .enablePlugins(JmhPlugin) - .dependsOn(core.jvm, atto.jvm, parserc.jvm) + .dependsOn(core.jvm, parserc.jvm) // ================================================================================= // DateTime library extensions diff --git a/modules/core/js/src/main/scala/cron4s/Cron.scala b/modules/core/js/src/main/scala/cron4s/Cron.scala new file mode 100644 index 00000000..f5e63b86 --- /dev/null +++ b/modules/core/js/src/main/scala/cron4s/Cron.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import scala.scalajs.js.annotation.JSExportTopLevel + +/** + * The entry point for parsing cron expressions + * + * @author Antonio Alonso Dominguez + */ +@JSExportTopLevel("Cron") +object Cron extends CronImpl(atto.parser) diff --git a/modules/core/jvm/src/main/scala/cron4s/Cron.scala b/modules/core/jvm/src/main/scala/cron4s/Cron.scala new file mode 100644 index 00000000..f5e63b86 --- /dev/null +++ b/modules/core/jvm/src/main/scala/cron4s/Cron.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import scala.scalajs.js.annotation.JSExportTopLevel + +/** + * The entry point for parsing cron expressions + * + * @author Antonio Alonso Dominguez + */ +@JSExportTopLevel("Cron") +object Cron extends CronImpl(atto.parser) diff --git a/modules/parserc/shared/src/main/scala/cron4s/Cron.scala b/modules/core/native/src/main/scala/cron4s/Cron.scala similarity index 100% rename from modules/parserc/shared/src/main/scala/cron4s/Cron.scala rename to modules/core/native/src/main/scala/cron4s/Cron.scala diff --git a/modules/core/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala b/modules/kernel/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala similarity index 100% rename from modules/core/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala rename to modules/kernel/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala diff --git a/modules/core/js/src/main/scala/cron4s/lib/js/package.scala b/modules/kernel/js/src/main/scala/cron4s/lib/js/package.scala similarity index 100% rename from modules/core/js/src/main/scala/cron4s/lib/js/package.scala rename to modules/kernel/js/src/main/scala/cron4s/lib/js/package.scala diff --git a/modules/core/shared/src/main/scala-2.12/compat.scala b/modules/kernel/shared/src/main/scala-2.12/compat.scala similarity index 100% rename from modules/core/shared/src/main/scala-2.12/compat.scala rename to modules/kernel/shared/src/main/scala-2.12/compat.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala b/modules/kernel/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/datetime/Stepper.scala b/modules/kernel/shared/src/main/scala-2/cron4s/datetime/Stepper.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/datetime/Stepper.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/datetime/Stepper.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/datetime/package.scala b/modules/kernel/shared/src/main/scala-2/cron4s/datetime/package.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/datetime/package.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/datetime/package.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/CronExpr.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/CronExpr.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/CronExpr.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/CronExpr.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/ops.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/ops.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/ops.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/ops.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/package.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/package.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/package.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/package.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/parts.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/parts.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/parts.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/parts.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/expr/wrappers.scala b/modules/kernel/shared/src/main/scala-2/cron4s/expr/wrappers.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/expr/wrappers.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/expr/wrappers.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala b/modules/kernel/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/validation/ops.scala b/modules/kernel/shared/src/main/scala-2/cron4s/validation/ops.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/validation/ops.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/validation/ops.scala diff --git a/modules/core/shared/src/main/scala-2/cron4s/validation/package.scala b/modules/kernel/shared/src/main/scala-2/cron4s/validation/package.scala similarity index 100% rename from modules/core/shared/src/main/scala-2/cron4s/validation/package.scala rename to modules/kernel/shared/src/main/scala-2/cron4s/validation/package.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala b/modules/kernel/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/datetime/Stepper.scala b/modules/kernel/shared/src/main/scala-3/cron4s/datetime/Stepper.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/datetime/Stepper.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/datetime/Stepper.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/datetime/package.scala b/modules/kernel/shared/src/main/scala-3/cron4s/datetime/package.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/datetime/package.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/datetime/package.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/CronExpr.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/CronExpr.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/CronExpr.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/CronExpr.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/ops.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/ops.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/ops.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/ops.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/package.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/package.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/package.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/package.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/parts.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/parts.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/parts.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/parts.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/expr/wrappers.scala b/modules/kernel/shared/src/main/scala-3/cron4s/expr/wrappers.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/expr/wrappers.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/expr/wrappers.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala b/modules/kernel/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/validation/ops.scala b/modules/kernel/shared/src/main/scala-3/cron4s/validation/ops.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/validation/ops.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/validation/ops.scala diff --git a/modules/core/shared/src/main/scala-3/cron4s/validation/package.scala b/modules/kernel/shared/src/main/scala-3/cron4s/validation/package.scala similarity index 100% rename from modules/core/shared/src/main/scala-3/cron4s/validation/package.scala rename to modules/kernel/shared/src/main/scala-3/cron4s/validation/package.scala diff --git a/modules/core/shared/src/main/scala/cron4s/CronField.scala b/modules/kernel/shared/src/main/scala/cron4s/CronField.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/CronField.scala rename to modules/kernel/shared/src/main/scala/cron4s/CronField.scala diff --git a/modules/core/shared/src/main/scala/cron4s/CronImpl.scala b/modules/kernel/shared/src/main/scala/cron4s/CronImpl.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/CronImpl.scala rename to modules/kernel/shared/src/main/scala/cron4s/CronImpl.scala diff --git a/modules/core/shared/src/main/scala/cron4s/CronUnit.scala b/modules/kernel/shared/src/main/scala/cron4s/CronUnit.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/CronUnit.scala rename to modules/kernel/shared/src/main/scala/cron4s/CronUnit.scala diff --git a/modules/core/shared/src/main/scala/cron4s/base/Enumerated.scala b/modules/kernel/shared/src/main/scala/cron4s/base/Enumerated.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/base/Enumerated.scala rename to modules/kernel/shared/src/main/scala/cron4s/base/Enumerated.scala diff --git a/modules/core/shared/src/main/scala/cron4s/base/Predicate.scala b/modules/kernel/shared/src/main/scala/cron4s/base/Predicate.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/base/Predicate.scala rename to modules/kernel/shared/src/main/scala/cron4s/base/Predicate.scala diff --git a/modules/core/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala b/modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala rename to modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala diff --git a/modules/core/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala b/modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala rename to modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala diff --git a/modules/core/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala b/modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala rename to modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala diff --git a/modules/core/shared/src/main/scala/cron4s/datetime/IsDateTime.scala b/modules/kernel/shared/src/main/scala/cron4s/datetime/IsDateTime.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/datetime/IsDateTime.scala rename to modules/kernel/shared/src/main/scala/cron4s/datetime/IsDateTime.scala diff --git a/modules/core/shared/src/main/scala/cron4s/datetime/errors.scala b/modules/kernel/shared/src/main/scala/cron4s/datetime/errors.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/datetime/errors.scala rename to modules/kernel/shared/src/main/scala/cron4s/datetime/errors.scala diff --git a/modules/core/shared/src/main/scala/cron4s/errors.scala b/modules/kernel/shared/src/main/scala/cron4s/errors.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/errors.scala rename to modules/kernel/shared/src/main/scala/cron4s/errors.scala diff --git a/modules/core/shared/src/main/scala/cron4s/expr/CronExprInstances.scala b/modules/kernel/shared/src/main/scala/cron4s/expr/CronExprInstances.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/expr/CronExprInstances.scala rename to modules/kernel/shared/src/main/scala/cron4s/expr/CronExprInstances.scala diff --git a/modules/core/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala b/modules/kernel/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala rename to modules/kernel/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala diff --git a/modules/core/shared/src/main/scala/cron4s/expr/FieldExpr.scala b/modules/kernel/shared/src/main/scala/cron4s/expr/FieldExpr.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/expr/FieldExpr.scala rename to modules/kernel/shared/src/main/scala/cron4s/expr/FieldExpr.scala diff --git a/modules/core/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala b/modules/kernel/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala rename to modules/kernel/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala diff --git a/modules/core/shared/src/main/scala/cron4s/expr/nodes.scala b/modules/kernel/shared/src/main/scala/cron4s/expr/nodes.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/expr/nodes.scala rename to modules/kernel/shared/src/main/scala/cron4s/expr/nodes.scala diff --git a/modules/core/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala b/modules/kernel/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala rename to modules/kernel/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala diff --git a/modules/core/shared/src/main/scala/cron4s/lib/javatime/package.scala b/modules/kernel/shared/src/main/scala/cron4s/lib/javatime/package.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/lib/javatime/package.scala rename to modules/kernel/shared/src/main/scala/cron4s/lib/javatime/package.scala diff --git a/modules/core/shared/src/main/scala/cron4s/package.scala b/modules/kernel/shared/src/main/scala/cron4s/package.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/package.scala rename to modules/kernel/shared/src/main/scala/cron4s/package.scala diff --git a/modules/core/shared/src/main/scala/cron4s/syntax/all.scala b/modules/kernel/shared/src/main/scala/cron4s/syntax/all.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/syntax/all.scala rename to modules/kernel/shared/src/main/scala/cron4s/syntax/all.scala diff --git a/modules/core/shared/src/main/scala/cron4s/syntax/cron.scala b/modules/kernel/shared/src/main/scala/cron4s/syntax/cron.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/syntax/cron.scala rename to modules/kernel/shared/src/main/scala/cron4s/syntax/cron.scala diff --git a/modules/core/shared/src/main/scala/cron4s/syntax/enumerated.scala b/modules/kernel/shared/src/main/scala/cron4s/syntax/enumerated.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/syntax/enumerated.scala rename to modules/kernel/shared/src/main/scala/cron4s/syntax/enumerated.scala diff --git a/modules/core/shared/src/main/scala/cron4s/syntax/field.scala b/modules/kernel/shared/src/main/scala/cron4s/syntax/field.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/syntax/field.scala rename to modules/kernel/shared/src/main/scala/cron4s/syntax/field.scala diff --git a/modules/core/shared/src/main/scala/cron4s/syntax/node.scala b/modules/kernel/shared/src/main/scala/cron4s/syntax/node.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/syntax/node.scala rename to modules/kernel/shared/src/main/scala/cron4s/syntax/node.scala diff --git a/modules/core/shared/src/main/scala/cron4s/syntax/predicate.scala b/modules/kernel/shared/src/main/scala/cron4s/syntax/predicate.scala similarity index 100% rename from modules/core/shared/src/main/scala/cron4s/syntax/predicate.scala rename to modules/kernel/shared/src/main/scala/cron4s/syntax/predicate.scala diff --git a/modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala b/modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala new file mode 100644 index 00000000..d457f6c0 --- /dev/null +++ b/modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s.parserc + +import cron4s.{CronImpl, parsing} + +import scala.scalajs.js.annotation.JSExportTopLevel + +/** + * The entry point for parsing cron expressions + * + * @author Antonio Alonso Dominguez + */ +@JSExportTopLevel("ParserCombinatorCron") +object Cron extends CronImpl(parsing.parse) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ff428253..79976890 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -48,7 +48,7 @@ object Dependencies { lazy val atto = Def.settings { libraryDependencies ++= Seq( - "org.tpolecat" %% "atto-core" % version.atto + "org.tpolecat" %%% "atto-core" % version.atto ) } @@ -60,7 +60,7 @@ object Dependencies { libraryDependencies += "org.scala-js" %% "scalajs-stubs" % "1.1.0" % Provided } - lazy val coreNative = coreJS ++ coreJVM + lazy val coreNative = coreJS ++ coreJVM ++ parserc lazy val testkit = Def.settings { libraryDependencies ++= Seq( diff --git a/tests/js/src/test/scala/cron4s/JsCronSpec.scala b/tests/js/src/test/scala/cron4s/JsCronSpec.scala new file mode 100644 index 00000000..ee1e3bfe --- /dev/null +++ b/tests/js/src/test/scala/cron4s/JsCronSpec.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + def parser = parsing.parse +} + +class AttoCronSpec extends AnyFlatSpec with CronSpec { + def parser = atto.parser +} + +class CronParserComparisonSpec extends AnyFlatSpec with Matchers { + import CronSpec._ + + "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { + forAll(CronSpec.validExpressions) { expr => + parsing.parse(expr) shouldBe atto.parser(expr) + } + } +} diff --git a/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala b/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala new file mode 100644 index 00000000..ee1e3bfe --- /dev/null +++ b/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + def parser = parsing.parse +} + +class AttoCronSpec extends AnyFlatSpec with CronSpec { + def parser = atto.parser +} + +class CronParserComparisonSpec extends AnyFlatSpec with Matchers { + import CronSpec._ + + "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { + forAll(CronSpec.validExpressions) { expr => + parsing.parse(expr) shouldBe atto.parser(expr) + } + } +} diff --git a/tests/native/src/test/scala/cron4s/NativeCronSpec.scala b/tests/native/src/test/scala/cron4s/NativeCronSpec.scala new file mode 100644 index 00000000..1c372228 --- /dev/null +++ b/tests/native/src/test/scala/cron4s/NativeCronSpec.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import org.scalatest.flatspec.AnyFlatSpec + +class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + def parser = parsing.parse +} diff --git a/tests/shared/src/test/scala/cron4s/CronSpec.scala b/tests/shared/src/test/scala/cron4s/CronSpec.scala index 0465c0e1..0988e224 100644 --- a/tests/shared/src/test/scala/cron4s/CronSpec.scala +++ b/tests/shared/src/test/scala/cron4s/CronSpec.scala @@ -133,21 +133,3 @@ trait CronSpec extends Matchers { this: AnyFlatSpec => } } - -class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { - def parser = parsing.parse -} - -class AttoCronSpec extends AnyFlatSpec with CronSpec { - def parser = atto.parser -} - -class CronParserComparisonSpec extends AnyFlatSpec with Matchers { - import CronSpec._ - - "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { - forAll(CronSpec.validExpressions) { expr => - parsing.parse(expr) shouldBe atto.parser(expr) - } - } -} From 0d973c8fe0a4092e5ea0225b4040edbb0472f9c7 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Thu, 19 Dec 2024 15:40:04 +0100 Subject: [PATCH 08/12] refactor: introduce a parser module that provides and ADT for parsers --- .../scala/cron4s/bench/ParserBenchmark.scala | 4 +- build.sbt | 20 +-- .../src/main/scala/cron4s/atto/Parser.scala | 115 +++++-------- .../src/main/scala/cron4s/atto/package.scala | 23 --- .../core/js/src/main/scala/cron4s/Cron.scala | 2 +- .../scala/cron4s/lib/js/JsDateInstance.scala | 0 .../main/scala/cron4s/lib/js/package.scala | 0 .../core/jvm/src/main/scala/cron4s/Cron.scala | 2 +- .../native/src/main/scala/cron4s/Cron.scala | 2 +- .../shared/src/main/scala-2.12/compat.scala | 0 .../cron4s/datetime/PredicateReducer.scala | 0 .../scala-2/cron4s/datetime/Stepper.scala | 0 .../scala-2/cron4s/datetime/package.scala | 0 .../main/scala-2/cron4s/expr/CronExpr.scala | 0 .../scala-2/cron4s/expr/FieldSelector.scala | 0 .../scala-2/cron4s/expr/NodeConversions.scala | 0 .../cron4s/expr/WrapperInstances.scala | 0 .../src/main/scala-2/cron4s/expr/ops.scala | 0 .../main/scala-2/cron4s/expr/package.scala | 0 .../src/main/scala-2/cron4s/expr/parts.scala | 0 .../main/scala-2/cron4s/expr/wrappers.scala | 0 .../cron4s/validation/NodeValidator.scala | 0 .../main/scala-2/cron4s/validation/ops.scala | 0 .../scala-2/cron4s/validation/package.scala | 0 .../cron4s/datetime/PredicateReducer.scala | 0 .../scala-3/cron4s/datetime/Stepper.scala | 0 .../scala-3/cron4s/datetime/package.scala | 0 .../main/scala-3/cron4s/expr/CronExpr.scala | 0 .../scala-3/cron4s/expr/FieldSelector.scala | 0 .../scala-3/cron4s/expr/NodeConversions.scala | 0 .../cron4s/expr/WrapperInstances.scala | 0 .../src/main/scala-3/cron4s/expr/ops.scala | 0 .../main/scala-3/cron4s/expr/package.scala | 0 .../src/main/scala-3/cron4s/expr/parts.scala | 0 .../main/scala-3/cron4s/expr/wrappers.scala | 0 .../cron4s/validation/NodeValidators.scala | 0 .../main/scala-3/cron4s/validation/ops.scala | 0 .../scala-3/cron4s/validation/package.scala | 0 .../src/main/scala/cron4s/CronField.scala | 0 .../src/main/scala/cron4s/CronImpl.scala | 4 +- .../src/main/scala/cron4s/CronUnit.scala | 0 .../src/main/scala/cron4s/ParserAdapter.scala | 109 ++++++++++++ .../main/scala/cron4s/base/Enumerated.scala | 0 .../main/scala/cron4s/base/Predicate.scala | 0 .../scala/cron4s/datetime/DateTimeCron.scala | 0 .../scala/cron4s/datetime/DateTimeNode.scala | 0 .../scala/cron4s/datetime/DateTimeUnit.scala | 0 .../scala/cron4s/datetime/IsDateTime.scala | 0 .../main/scala/cron4s/datetime/errors.scala | 0 .../shared/src/main/scala/cron4s/errors.scala | 0 .../scala/cron4s/expr/CronExprInstances.scala | 0 .../cron4s/expr/DateCronExprInstances.scala | 0 .../main/scala/cron4s/expr/FieldExpr.scala | 0 .../cron4s/expr/TimeCronExprInstances.scala | 0 .../src/main/scala/cron4s/expr/nodes.scala | 0 .../lib/javatime/JavaTemporalInstance.scala | 0 .../scala/cron4s/lib/javatime/package.scala | 0 .../src/main/scala/cron4s/package.scala | 2 - .../src/main/scala/cron4s/syntax/all.scala | 0 .../src/main/scala/cron4s/syntax/cron.scala | 0 .../main/scala/cron4s/syntax/enumerated.scala | 0 .../src/main/scala/cron4s/syntax/field.scala | 0 .../src/main/scala/cron4s/syntax/node.scala | 0 .../main/scala/cron4s/syntax/predicate.scala | 0 .../main/scala/cron4s/parser/package.scala | 162 ++++++++++++++++++ .../src/main/scala/cron4s/parserc/Cron.scala | 29 ---- .../src/main/scala/cron4s/parsing/base.scala | 6 +- .../src/main/scala/cron4s/parsing/lexer.scala | 2 +- .../main/scala/cron4s/parsing/package.scala | 18 +- .../main/scala/cron4s/parsing/parser.scala | 100 +++++------ .../js/src/test/scala/cron4s/JsCronSpec.scala | 6 +- .../src/test/scala/cron4s/JvmCronSpec.scala | 6 +- .../test/scala/cron4s/NativeCronSpec.scala | 2 +- .../src/test/scala/cron4s/CronSpec.scala | 2 +- .../scala/cron4s/parsing/ParserSpec.scala | 80 ++++----- 75 files changed, 433 insertions(+), 263 deletions(-) delete mode 100644 modules/atto/shared/src/main/scala/cron4s/atto/package.scala rename modules/{kernel => core}/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala (100%) rename modules/{kernel => core}/js/src/main/scala/cron4s/lib/js/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2.12/compat.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/datetime/Stepper.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/datetime/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/CronExpr.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/ops.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/parts.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/expr/wrappers.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/validation/ops.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-2/cron4s/validation/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/datetime/Stepper.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/datetime/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/CronExpr.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/ops.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/parts.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/expr/wrappers.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/validation/ops.scala (100%) rename modules/{kernel => core}/shared/src/main/scala-3/cron4s/validation/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/CronField.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/CronImpl.scala (95%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/CronUnit.scala (100%) create mode 100644 modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala rename modules/{kernel => core}/shared/src/main/scala/cron4s/base/Enumerated.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/base/Predicate.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/datetime/IsDateTime.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/datetime/errors.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/errors.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/expr/CronExprInstances.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/expr/FieldExpr.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/expr/nodes.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/lib/javatime/package.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/package.scala (93%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/syntax/all.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/syntax/cron.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/syntax/enumerated.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/syntax/field.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/syntax/node.scala (100%) rename modules/{kernel => core}/shared/src/main/scala/cron4s/syntax/predicate.scala (100%) create mode 100644 modules/parser/shared/src/main/scala/cron4s/parser/package.scala delete mode 100644 modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala diff --git a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala index fe69846a..6e755e14 100644 --- a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala +++ b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala @@ -38,8 +38,8 @@ class ParserBenchmark { var cronString: String = _ @Benchmark - def parserCombinators() = parsing.parse(cronString) + def parserCombinators() = parsing.Parser.parse(cronString) @Benchmark - def attoParser() = atto.parser(cronString) + def attoParser() = atto.Parser.parse(cronString) } diff --git a/build.sbt b/build.sbt index 735066bf..c7aeac17 100644 --- a/build.sbt +++ b/build.sbt @@ -279,10 +279,10 @@ lazy val cron4sJS = (project in file(".js")) .settings(commonJsSettings: _*) .settings(noPublishSettings) .enablePlugins(ScalaJSPlugin) - .aggregate(core.js, kernel.js, atto.js, momentjs, circe.js, decline.js, testkit.js, tests.js) + .aggregate(core.js, parser.js, atto.js, momentjs, circe.js, decline.js, testkit.js, tests.js) .dependsOn( core.js, - kernel.js, + parser.js, atto.js, momentjs, circe.js, @@ -301,7 +301,7 @@ lazy val cron4sJVM = (project in file(".jvm")) .settings(consoleSettings) .settings(noPublishSettings) .aggregate( - kernel.jvm, + parser.jvm, core.jvm, atto.jvm, joda, @@ -357,11 +357,11 @@ lazy val docs = project // Main modules // ================================================================================= -lazy val kernel = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/kernel")) +lazy val parser = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/parser")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) .settings( - name := "kernel", - moduleName := "cron4s-kernel" + name := "parser", + moduleName := "cron4s-parser" ) .settings(commonSettings) .settings(publishSettings) @@ -371,7 +371,7 @@ lazy val kernel = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file .jvmSettings(commonJvmSettings) .jvmSettings(consoleSettings) .jvmSettings(Dependencies.coreJVM) - .jvmSettings(mimaSettings("kernel")) + .jvmSettings(mimaSettings("parser")) .nativeSettings(Dependencies.coreNative) lazy val core = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/core")) @@ -393,7 +393,7 @@ lazy val core = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file(" .jvmConfigure(_.dependsOn(atto.jvm)) .nativeSettings(Dependencies.coreNative) .nativeConfigure(_.dependsOn(parserc.native)) - .dependsOn(kernel) + .dependsOn(parser) lazy val parserc = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/parserc")) @@ -410,7 +410,7 @@ lazy val parserc = .jvmSettings(commonJvmSettings) .jvmSettings(Dependencies.coreJVM) .nativeSettings(Dependencies.coreNative) - .dependsOn(kernel) + .dependsOn(parser) lazy val atto = (crossProject(JSPlatform, JVMPlatform) in file("modules/atto")) .enablePlugins(AutomateHeaderPlugin, ScalafmtPlugin, MimaPlugin) @@ -425,7 +425,7 @@ lazy val atto = (crossProject(JSPlatform, JVMPlatform) in file("modules/atto")) .jsSettings(Dependencies.coreJS) .jvmSettings(commonJvmSettings) .jvmSettings(Dependencies.coreJVM) - .dependsOn(kernel) + .dependsOn(parser) lazy val testkit = (crossProject(JSPlatform, JVMPlatform, NativePlatform) in file("modules/testkit")) diff --git a/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala b/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala index fac57fd1..8b7ea94b 100644 --- a/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala +++ b/modules/atto/shared/src/main/scala/cron4s/atto/Parser.scala @@ -19,12 +19,11 @@ package cron4s.atto import _root_.atto.{Parser => AttoParser, _} import atto.Atto._ import cats.implicits._ -import cron4s.CronField._ -import cron4s.CronUnit._ -import cron4s._ -import cron4s.expr._ -private object Parser { +object Parser extends cron4s.parser.Parser { + + import cron4s.parser._ + import cron4s.parser.Node._ private def oneOrTwoDigitsPositiveInt: AttoParser[Int] = { @@ -60,122 +59,102 @@ private object Parser { // ---------------------------------------- // Seconds - - val seconds: AttoParser[ConstNode[Second]] = - sexagesimal.map(ConstNode[Second](_)) + private val seconds: AttoParser[ConstNode] = sexagesimal.map(ConstNode(_)) // Minutes - val minutes: AttoParser[ConstNode[Minute]] = - sexagesimal.map(ConstNode[Minute](_)) + private val minutes: AttoParser[ConstNode] = sexagesimal.map(ConstNode(_)) // Hours - val hours: AttoParser[ConstNode[Hour]] = - oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x < 24)).map(ConstNode[Hour](_)) + private val hours: AttoParser[ConstNode] = + oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x < 24)).map(ConstNode(_)) // Days Of Month - val daysOfMonth: AttoParser[ConstNode[DayOfMonth]] = - oneOrTwoDigitsPositiveInt.filter(x => (x >= 1) && (x <= 31)).map(ConstNode[DayOfMonth](_)) + private val daysOfMonth: AttoParser[ConstNode] = + oneOrTwoDigitsPositiveInt.filter(x => (x >= 1) && (x <= 31)).map(ConstNode(_)) // Months - private[this] val numericMonths = - oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x <= 12)).map(ConstNode[Month](_)) + private[this] val numericMonths: AttoParser[ConstNode] = + oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x <= 12)).map(ConstNode(_)) - private[this] val textualMonths = + private[this] val textualMonths: AttoParser[ConstNode] = literal.filter(Months.textValues.contains).map { value => val index = Months.textValues.indexOf(value) - ConstNode[Month](index + 1, Some(value)) + ConstNode(index + 1, Some(value)) } - val months: AttoParser[ConstNode[Month]] = + private val months: AttoParser[ConstNode] = textualMonths | numericMonths // Days Of Week - private[this] val numericDaysOfWeek = - oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x <= 6)).map(ConstNode[DayOfWeek](_)) + private[this] val numericDaysOfWeek: AttoParser[ConstNode] = + oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x <= 6)).map(ConstNode(_)) - private[this] val textualDaysOfWeek = + private[this] val textualDaysOfWeek: AttoParser[ConstNode] = literal.filter(DaysOfWeek.textValues.contains).map { value => val index = DaysOfWeek.textValues.indexOf(value) - ConstNode[DayOfWeek](index, Some(value)) + ConstNode(index, Some(value)) } - val daysOfWeek: AttoParser[ConstNode[DayOfWeek]] = + private val daysOfWeek: AttoParser[ConstNode] = textualDaysOfWeek | numericDaysOfWeek // ---------------------------------------- // Field-Based Expression Atoms // ---------------------------------------- - def each[F <: CronField](implicit unit: CronUnit[F]): AttoParser[EachNode[F]] = - asterisk.as(EachNode[F]) + private def each: AttoParser[EachNode.type] = asterisk.as(EachNode) - def any[F <: CronField](implicit unit: CronUnit[F]): AttoParser[AnyNode[F]] = - questionMark.as(AnyNode[F]) + private def any: AttoParser[AnyNode.type] = questionMark.as(AnyNode) - def between[F <: CronField](base: AttoParser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): AttoParser[BetweenNode[F]] = + private def between(base: AttoParser[ConstNode]): AttoParser[BetweenNode] = for { min <- base <~ hyphen max <- base - } yield BetweenNode[F](min, max) + } yield BetweenNode(min, max) - def several[F <: CronField](base: AttoParser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): AttoParser[SeveralNode[F]] = { - def compose(b: => AttoParser[EnumerableNode[F]]) = + private def several(base: AttoParser[ConstNode]): AttoParser[SeveralNode] = { + def compose(b: => AttoParser[EnumerableNode]) = sepBy(b, comma) .collect { case first :: second :: tail => SeveralNode(first, second, tail: _*) } - compose(between(base).map(between2Enumerable) | base.map(const2Enumerable)) + compose(between(base) | base) } - def every[F <: CronField](base: AttoParser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): AttoParser[EveryNode[F]] = { - def compose(b: => AttoParser[DivisibleNode[F]]) = + private def every(base: AttoParser[ConstNode]): AttoParser[EveryNode] = { + def compose(b: => AttoParser[DivisibleNode]) = ((b <~ slash) ~ oneOrTwoDigitsPositiveInt.filter(_ > 0)).map { - case (exp, freq) => EveryNode[F](exp, freq) + case (exp, freq) => EveryNode(exp, freq) } - - compose( - several(base).map(several2Divisible) | - between(base).map(between2Divisible) | - each[F].map(each2Divisible) - ) + compose(several(base) | between(base) | each) } // ---------------------------------------- // AST Parsing & Building // ---------------------------------------- - def field[F <: CronField](base: AttoParser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): AttoParser[FieldNode[F]] = - every(base).map(every2Field) | - several(base).map(several2Field) | - between(base).map(between2Field) | - base.map(const2Field) | - each[F].map(each2Field) - - def fieldWithAny[F <: CronField](base: AttoParser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): AttoParser[FieldNodeWithAny[F]] = - every(base).map(every2FieldWithAny) | - several(base).map(several2FieldWithAny) | - between(base).map(between2FieldWithAny) | - base.map(const2FieldWithAny) | - each[F].map(each2FieldWithAny) | - any[F].map(any2FieldWithAny) - - val cron: AttoParser[CronExpr] = for { + private def field(base: AttoParser[ConstNode]): AttoParser[NodeWithoutAny] = + every(base) | + several(base) | + between(base) | + base | + each + + private def fieldWithAny(base: AttoParser[ConstNode]): AttoParser[Node] = + every(base) | + several(base) | + between(base) | + base | + each | + any + + private val cron: AttoParser[CronExpr] = for { sec <- field(seconds) <~ blank min <- field(minutes) <~ blank hour <- field(hours) <~ blank diff --git a/modules/atto/shared/src/main/scala/cron4s/atto/package.scala b/modules/atto/shared/src/main/scala/cron4s/atto/package.scala deleted file mode 100644 index eb698830..00000000 --- a/modules/atto/shared/src/main/scala/cron4s/atto/package.scala +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2017 Antonio Alonso Dominguez - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cron4s - -package object atto { - - val parser: Parser = atto.Parser.parse - -} diff --git a/modules/core/js/src/main/scala/cron4s/Cron.scala b/modules/core/js/src/main/scala/cron4s/Cron.scala index f5e63b86..afcb1b8f 100644 --- a/modules/core/js/src/main/scala/cron4s/Cron.scala +++ b/modules/core/js/src/main/scala/cron4s/Cron.scala @@ -24,4 +24,4 @@ import scala.scalajs.js.annotation.JSExportTopLevel * @author Antonio Alonso Dominguez */ @JSExportTopLevel("Cron") -object Cron extends CronImpl(atto.parser) +object Cron extends CronImpl(atto.Parser) diff --git a/modules/kernel/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala b/modules/core/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala similarity index 100% rename from modules/kernel/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala rename to modules/core/js/src/main/scala/cron4s/lib/js/JsDateInstance.scala diff --git a/modules/kernel/js/src/main/scala/cron4s/lib/js/package.scala b/modules/core/js/src/main/scala/cron4s/lib/js/package.scala similarity index 100% rename from modules/kernel/js/src/main/scala/cron4s/lib/js/package.scala rename to modules/core/js/src/main/scala/cron4s/lib/js/package.scala diff --git a/modules/core/jvm/src/main/scala/cron4s/Cron.scala b/modules/core/jvm/src/main/scala/cron4s/Cron.scala index f5e63b86..afcb1b8f 100644 --- a/modules/core/jvm/src/main/scala/cron4s/Cron.scala +++ b/modules/core/jvm/src/main/scala/cron4s/Cron.scala @@ -24,4 +24,4 @@ import scala.scalajs.js.annotation.JSExportTopLevel * @author Antonio Alonso Dominguez */ @JSExportTopLevel("Cron") -object Cron extends CronImpl(atto.parser) +object Cron extends CronImpl(atto.Parser) diff --git a/modules/core/native/src/main/scala/cron4s/Cron.scala b/modules/core/native/src/main/scala/cron4s/Cron.scala index 2965cf1c..b6e40d5f 100644 --- a/modules/core/native/src/main/scala/cron4s/Cron.scala +++ b/modules/core/native/src/main/scala/cron4s/Cron.scala @@ -24,4 +24,4 @@ import scala.scalajs.js.annotation.JSExportTopLevel * @author Antonio Alonso Dominguez */ @JSExportTopLevel("Cron") -object Cron extends CronImpl(parsing.parse) +object Cron extends CronImpl(parsing.Parser) diff --git a/modules/kernel/shared/src/main/scala-2.12/compat.scala b/modules/core/shared/src/main/scala-2.12/compat.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2.12/compat.scala rename to modules/core/shared/src/main/scala-2.12/compat.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala b/modules/core/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala rename to modules/core/shared/src/main/scala-2/cron4s/datetime/PredicateReducer.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/datetime/Stepper.scala b/modules/core/shared/src/main/scala-2/cron4s/datetime/Stepper.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/datetime/Stepper.scala rename to modules/core/shared/src/main/scala-2/cron4s/datetime/Stepper.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/datetime/package.scala b/modules/core/shared/src/main/scala-2/cron4s/datetime/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/datetime/package.scala rename to modules/core/shared/src/main/scala-2/cron4s/datetime/package.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/CronExpr.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/CronExpr.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/CronExpr.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/CronExpr.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/FieldSelector.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/NodeConversions.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/WrapperInstances.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/ops.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/ops.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/ops.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/ops.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/package.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/package.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/package.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/parts.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/parts.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/parts.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/parts.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/expr/wrappers.scala b/modules/core/shared/src/main/scala-2/cron4s/expr/wrappers.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/expr/wrappers.scala rename to modules/core/shared/src/main/scala-2/cron4s/expr/wrappers.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala b/modules/core/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala rename to modules/core/shared/src/main/scala-2/cron4s/validation/NodeValidator.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/validation/ops.scala b/modules/core/shared/src/main/scala-2/cron4s/validation/ops.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/validation/ops.scala rename to modules/core/shared/src/main/scala-2/cron4s/validation/ops.scala diff --git a/modules/kernel/shared/src/main/scala-2/cron4s/validation/package.scala b/modules/core/shared/src/main/scala-2/cron4s/validation/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-2/cron4s/validation/package.scala rename to modules/core/shared/src/main/scala-2/cron4s/validation/package.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala b/modules/core/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala rename to modules/core/shared/src/main/scala-3/cron4s/datetime/PredicateReducer.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/datetime/Stepper.scala b/modules/core/shared/src/main/scala-3/cron4s/datetime/Stepper.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/datetime/Stepper.scala rename to modules/core/shared/src/main/scala-3/cron4s/datetime/Stepper.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/datetime/package.scala b/modules/core/shared/src/main/scala-3/cron4s/datetime/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/datetime/package.scala rename to modules/core/shared/src/main/scala-3/cron4s/datetime/package.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/CronExpr.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/CronExpr.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/CronExpr.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/CronExpr.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/FieldSelector.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/NodeConversions.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/WrapperInstances.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/ops.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/ops.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/ops.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/ops.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/package.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/package.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/package.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/parts.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/parts.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/parts.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/parts.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/expr/wrappers.scala b/modules/core/shared/src/main/scala-3/cron4s/expr/wrappers.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/expr/wrappers.scala rename to modules/core/shared/src/main/scala-3/cron4s/expr/wrappers.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala b/modules/core/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala rename to modules/core/shared/src/main/scala-3/cron4s/validation/NodeValidators.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/validation/ops.scala b/modules/core/shared/src/main/scala-3/cron4s/validation/ops.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/validation/ops.scala rename to modules/core/shared/src/main/scala-3/cron4s/validation/ops.scala diff --git a/modules/kernel/shared/src/main/scala-3/cron4s/validation/package.scala b/modules/core/shared/src/main/scala-3/cron4s/validation/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala-3/cron4s/validation/package.scala rename to modules/core/shared/src/main/scala-3/cron4s/validation/package.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/CronField.scala b/modules/core/shared/src/main/scala/cron4s/CronField.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/CronField.scala rename to modules/core/shared/src/main/scala/cron4s/CronField.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/CronImpl.scala b/modules/core/shared/src/main/scala/cron4s/CronImpl.scala similarity index 95% rename from modules/kernel/shared/src/main/scala/cron4s/CronImpl.scala rename to modules/core/shared/src/main/scala/cron4s/CronImpl.scala index a352e536..7b8c05b7 100644 --- a/modules/kernel/shared/src/main/scala/cron4s/CronImpl.scala +++ b/modules/core/shared/src/main/scala/cron4s/CronImpl.scala @@ -16,6 +16,8 @@ package cron4s +import cron4s.parser.Parser + import scala.util.Try private[cron4s] class CronImpl(parser: Parser) { @@ -40,7 +42,7 @@ private[cron4s] class CronImpl(parser: Parser) { */ @inline def parse(e: String): Either[Error, CronExpr] = - parser(e).flatMap(validation.validateCron) + ParserAdapter.adapt(parser)(e).flatMap(validation.validateCron) /** * Parses the given cron expression into a cron AST using Try as return type diff --git a/modules/kernel/shared/src/main/scala/cron4s/CronUnit.scala b/modules/core/shared/src/main/scala/cron4s/CronUnit.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/CronUnit.scala rename to modules/core/shared/src/main/scala/cron4s/CronUnit.scala diff --git a/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala b/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala new file mode 100644 index 00000000..62b42c01 --- /dev/null +++ b/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala @@ -0,0 +1,109 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +object ParserAdapter { + + import cron4s.parser._ + import cron4s.parser.Node._ + + def adapt(parser: Parser)(input: String): Either[cron4s.Error, cron4s.CronExpr] = + parser + .parse(input) + .fold(e => Left(mapError(e)), v => Right(mapExpr(v))) + + private def mapError(error: cron4s.parser.Error): cron4s.Error = + error match { + case ExprTooShort => cron4s.ExprTooShort + case e: ParseFailed => + cron4s.ParseFailed(expected = e.expected, position = e.position, found = e.found) + } + + private def mapExpr(expr: CronExpr): cron4s.CronExpr = { + cron4s.expr.CronExpr( + seconds = mapNode[CronField.Second](expr.seconds), + minutes = mapNode[CronField.Minute](expr.minutes), + hours = mapNode[CronField.Hour](expr.hours), + daysOfMonth = mapNodeWithAny[CronField.DayOfMonth](expr.daysOfMonth), + months = mapNode[CronField.Month](expr.months), + daysOfWeek = mapNodeWithAny[CronField.DayOfWeek](expr.daysOfWeek) + ) + } + + private def mapNode[F <: cron4s.CronField]( + node: NodeWithoutAny + )(implicit unit: cron4s.CronUnit[F]): cron4s.expr.FieldNode[F] = + node match { + case Node.EachNode => cron4s.expr.EachNode[F] + case n: Node.ConstNode => mapConst[F](n) + case n: Node.BetweenNode => mapBetweenNode[F](n) + case n: Node.SeveralNode => mapSeveral[F](n) + case n: Node.EveryNode => cron4s.expr.EveryNode[F](mapDivisible(n.base), n.freq) + } + + private def mapNodeWithAny[F <: cron4s.CronField]( + node: Node + )(implicit unit: cron4s.CronUnit[F]): cron4s.expr.FieldNodeWithAny[F] = + node match { + case Node.EachNode => cron4s.expr.EachNode[F] + case Node.AnyNode => cron4s.expr.AnyNode[F] + case n: Node.ConstNode => mapConst[F](n) + case n: Node.BetweenNode => mapBetweenNode[F](n) + case n: Node.SeveralNode => mapSeveral[F](n) + case n: Node.EveryNode => cron4s.expr.EveryNode[F](mapDivisible(n.base), n.freq) + } + + private def mapDivisible[F <: CronField](divisible: DivisibleNode)(implicit + unit: cron4s.CronUnit[F] + ): cron4s.expr.DivisibleNode[F] = + divisible match { + case n: Node.BetweenNode => mapBetweenNode[F](n) + case Node.EachNode => cron4s.expr.EachNode[F] + case n: Node.SeveralNode => mapSeveral[F](n) + } + + private def mapSeveral[F <: CronField](n: Node.SeveralNode)(implicit + unit: cron4s.CronUnit[F] + ): cron4s.expr.SeveralNode[F] = cron4s.expr.SeveralNode.apply[F]( + first = mapEnumerable[F](n.head), + second = mapEnumerable[F](n.tail.head), + tail = n.tail.tail.map(node => mapEnumerable[F](node)): _* + ) + + private def mapEnumerable[F <: CronField](enumerable: EnumerableNode)(implicit + unit: cron4s.CronUnit[F] + ): cron4s.expr.EnumerableNode[F] = + enumerable match { + case n: Node.BetweenNode => mapBetweenNode[F](n) + case n: Node.ConstNode => mapConst[F](n) + } + + private def mapBetweenNode[F <: CronField](n: Node.BetweenNode)(implicit + unit: cron4s.CronUnit[F] + ): cron4s.expr.BetweenNode[F] = { + cron4s.expr.BetweenNode( + begin = mapConst[F](n.begin), + end = mapConst[F](n.end) + ) + } + + private def mapConst[F <: CronField](n: Node.ConstNode)(implicit + unit: cron4s.CronUnit[F] + ): cron4s.expr.ConstNode[F] = + cron4s.expr.ConstNode(value = n.value, textValue = n.textValue) + +} diff --git a/modules/kernel/shared/src/main/scala/cron4s/base/Enumerated.scala b/modules/core/shared/src/main/scala/cron4s/base/Enumerated.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/base/Enumerated.scala rename to modules/core/shared/src/main/scala/cron4s/base/Enumerated.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/base/Predicate.scala b/modules/core/shared/src/main/scala/cron4s/base/Predicate.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/base/Predicate.scala rename to modules/core/shared/src/main/scala/cron4s/base/Predicate.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala b/modules/core/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala rename to modules/core/shared/src/main/scala/cron4s/datetime/DateTimeCron.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala b/modules/core/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala rename to modules/core/shared/src/main/scala/cron4s/datetime/DateTimeNode.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala b/modules/core/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala rename to modules/core/shared/src/main/scala/cron4s/datetime/DateTimeUnit.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/datetime/IsDateTime.scala b/modules/core/shared/src/main/scala/cron4s/datetime/IsDateTime.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/datetime/IsDateTime.scala rename to modules/core/shared/src/main/scala/cron4s/datetime/IsDateTime.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/datetime/errors.scala b/modules/core/shared/src/main/scala/cron4s/datetime/errors.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/datetime/errors.scala rename to modules/core/shared/src/main/scala/cron4s/datetime/errors.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/errors.scala b/modules/core/shared/src/main/scala/cron4s/errors.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/errors.scala rename to modules/core/shared/src/main/scala/cron4s/errors.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/expr/CronExprInstances.scala b/modules/core/shared/src/main/scala/cron4s/expr/CronExprInstances.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/expr/CronExprInstances.scala rename to modules/core/shared/src/main/scala/cron4s/expr/CronExprInstances.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala b/modules/core/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala rename to modules/core/shared/src/main/scala/cron4s/expr/DateCronExprInstances.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/expr/FieldExpr.scala b/modules/core/shared/src/main/scala/cron4s/expr/FieldExpr.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/expr/FieldExpr.scala rename to modules/core/shared/src/main/scala/cron4s/expr/FieldExpr.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala b/modules/core/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala rename to modules/core/shared/src/main/scala/cron4s/expr/TimeCronExprInstances.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/expr/nodes.scala b/modules/core/shared/src/main/scala/cron4s/expr/nodes.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/expr/nodes.scala rename to modules/core/shared/src/main/scala/cron4s/expr/nodes.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala b/modules/core/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala rename to modules/core/shared/src/main/scala/cron4s/lib/javatime/JavaTemporalInstance.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/lib/javatime/package.scala b/modules/core/shared/src/main/scala/cron4s/lib/javatime/package.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/lib/javatime/package.scala rename to modules/core/shared/src/main/scala/cron4s/lib/javatime/package.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/package.scala b/modules/core/shared/src/main/scala/cron4s/package.scala similarity index 93% rename from modules/kernel/shared/src/main/scala/cron4s/package.scala rename to modules/core/shared/src/main/scala/cron4s/package.scala index 98ba7b01..e43f3083 100644 --- a/modules/kernel/shared/src/main/scala/cron4s/package.scala +++ b/modules/core/shared/src/main/scala/cron4s/package.scala @@ -19,6 +19,4 @@ import cron4s.expr.NodeConversions package object cron4s extends AllSyntax with NodeConversions { type CronExpr = expr.CronExpr - - type Parser = String => Either[Error, CronExpr] } diff --git a/modules/kernel/shared/src/main/scala/cron4s/syntax/all.scala b/modules/core/shared/src/main/scala/cron4s/syntax/all.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/syntax/all.scala rename to modules/core/shared/src/main/scala/cron4s/syntax/all.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/syntax/cron.scala b/modules/core/shared/src/main/scala/cron4s/syntax/cron.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/syntax/cron.scala rename to modules/core/shared/src/main/scala/cron4s/syntax/cron.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/syntax/enumerated.scala b/modules/core/shared/src/main/scala/cron4s/syntax/enumerated.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/syntax/enumerated.scala rename to modules/core/shared/src/main/scala/cron4s/syntax/enumerated.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/syntax/field.scala b/modules/core/shared/src/main/scala/cron4s/syntax/field.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/syntax/field.scala rename to modules/core/shared/src/main/scala/cron4s/syntax/field.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/syntax/node.scala b/modules/core/shared/src/main/scala/cron4s/syntax/node.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/syntax/node.scala rename to modules/core/shared/src/main/scala/cron4s/syntax/node.scala diff --git a/modules/kernel/shared/src/main/scala/cron4s/syntax/predicate.scala b/modules/core/shared/src/main/scala/cron4s/syntax/predicate.scala similarity index 100% rename from modules/kernel/shared/src/main/scala/cron4s/syntax/predicate.scala rename to modules/core/shared/src/main/scala/cron4s/syntax/predicate.scala diff --git a/modules/parser/shared/src/main/scala/cron4s/parser/package.scala b/modules/parser/shared/src/main/scala/cron4s/parser/package.scala new file mode 100644 index 00000000..391e7b25 --- /dev/null +++ b/modules/parser/shared/src/main/scala/cron4s/parser/package.scala @@ -0,0 +1,162 @@ +/* + * Copyright 2017 Antonio Alonso Dominguez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cron4s + +import cats.data.NonEmptyList + +package parser { + trait Parser { + def parse(input: String): Either[Error, CronExpr] + } + + sealed trait CronUnit + object CronUnit { + sealed trait Second extends CronUnit + case object Second extends Second + + sealed trait Minute extends CronUnit + case object Minute extends Minute + + sealed trait Hour extends CronUnit + case object Hour extends Hour + + sealed trait DayOfMonth extends CronUnit + case object DayOfMonth extends DayOfMonth + + sealed trait Month extends CronUnit + case object Month extends Month + + sealed trait DayOfWeek extends CronUnit + case object DayOfWeek extends DayOfWeek + + final val All: List[CronUnit] = + List(Second, Minute, Hour, DayOfMonth, Month, DayOfWeek) + } + + sealed trait Node + + object Node { + sealed trait EnumerableNode + sealed trait DivisibleNode + sealed trait NodeWithoutAny + + case object EachNode extends Node with NodeWithoutAny with DivisibleNode { + override val toString = "*" + } + + case object AnyNode extends Node { + override def toString: String = "?" + } + + final case class ConstNode( + value: Int, + textValue: Option[String] = None + ) extends Node with NodeWithoutAny with EnumerableNode { + override lazy val toString: String = textValue.getOrElse(value.toString) + } + + final case class BetweenNode( + begin: ConstNode, + end: ConstNode + ) extends Node with NodeWithoutAny with EnumerableNode with DivisibleNode { + override lazy val toString: String = s"$begin-$end" + } + + final case class SeveralNode( + head: EnumerableNode, + tail: NonEmptyList[EnumerableNode] + ) extends Node with NodeWithoutAny with DivisibleNode { + lazy val values: List[EnumerableNode] = head :: tail.toList + override lazy val toString: String = values.mkString(",") + } + + object SeveralNode { + @inline def apply( + first: EnumerableNode, + second: EnumerableNode, + tail: EnumerableNode* + ): SeveralNode = + new SeveralNode(first, NonEmptyList.of(second, tail: _*)) + + def fromSeq(xs: Seq[EnumerableNode]): Option[SeveralNode] = { + def splitSeq( + xs: Seq[EnumerableNode] + ): Option[(EnumerableNode, EnumerableNode, Seq[EnumerableNode])] = + if (xs.length < 2) None + else Some((xs.head, xs.tail.head, xs.tail.tail)) + + splitSeq(xs).map { + case (first, second, tail) => SeveralNode(first, second, tail: _*) + } + } + + } + + final case class EveryNode( + base: DivisibleNode, + freq: Int + ) extends Node with NodeWithoutAny { + override lazy val toString: String = s"$base/$freq" + } + } + + object Months { + val textValues = IndexedSeq( + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "ago", + "sep", + "oct", + "nov", + "dec" + ) + } + object DaysOfWeek { + val textValues = IndexedSeq("mon", "tue", "wed", "thu", "fri", "sat", "sun") + } + + final case class CronExpr( + seconds: Node.NodeWithoutAny, + minutes: Node.NodeWithoutAny, + hours: Node.NodeWithoutAny, + daysOfMonth: Node, + months: Node.NodeWithoutAny, + daysOfWeek: Node + ) + + sealed abstract class Error(description: String) extends Exception(description) + + case object ExprTooShort extends Error("The provided expression was too short") + + final case class ParseFailed(expected: String, position: Int, found: Option[String]) + extends Error(s"$expected at position ${position}${found.fold("")(f => s" but found '$f'")}") + + object ParseFailed { + def apply(expected: String, position: Int, found: Option[String] = None): ParseFailed = + new ParseFailed(expected, position, found) + + @deprecated("Use the other apply method signature with optional 'found'", "0.6.1") + def apply(msg: String, found: String, position: Int): ParseFailed = + ParseFailed(msg, position, Some(found)) + } + +} diff --git a/modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala b/modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala deleted file mode 100644 index d457f6c0..00000000 --- a/modules/parserc/shared/src/main/scala/cron4s/parserc/Cron.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2017 Antonio Alonso Dominguez - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cron4s.parserc - -import cron4s.{CronImpl, parsing} - -import scala.scalajs.js.annotation.JSExportTopLevel - -/** - * The entry point for parsing cron expressions - * - * @author Antonio Alonso Dominguez - */ -@JSExportTopLevel("ParserCombinatorCron") -object Cron extends CronImpl(parsing.parse) diff --git a/modules/parserc/shared/src/main/scala/cron4s/parsing/base.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/base.scala index 396f2c71..ffd33579 100644 --- a/modules/parserc/shared/src/main/scala/cron4s/parsing/base.scala +++ b/modules/parserc/shared/src/main/scala/cron4s/parsing/base.scala @@ -17,11 +17,13 @@ package cron4s package parsing +import cron4s.parser.{ExprTooShort, ParseFailed} + import scala.util.parsing.combinator.Parsers -import scala.util.parsing.input.{Position, NoPosition} +import scala.util.parsing.input.{NoPosition, Position} private[parsing] trait BaseParser extends Parsers { - protected def handleError(err: NoSuccess): _root_.cron4s.Error = + protected def handleError(err: NoSuccess): _root_.cron4s.parser.Error = err.next.pos match { case NoPosition => ExprTooShort case pos: Position => diff --git a/modules/parserc/shared/src/main/scala/cron4s/parsing/lexer.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/lexer.scala index dc404037..29f87323 100644 --- a/modules/parserc/shared/src/main/scala/cron4s/parsing/lexer.scala +++ b/modules/parserc/shared/src/main/scala/cron4s/parsing/lexer.scala @@ -91,7 +91,7 @@ object CronLexer extends RegexParsers with BaseParser { number | text | hyphen | slash | comma | asterisk | questionMark | blank ) - def tokenize(expr: String): Either[_root_.cron4s.Error, List[CronToken]] = + def tokenize(expr: String): Either[_root_.cron4s.parser.Error, List[CronToken]] = parse(tokens, expr) match { case err: NoSuccess => Left(handleError(err)) case Success(result, _) => Right(result) diff --git a/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala index c37fed45..5ef0e8d5 100644 --- a/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala +++ b/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala @@ -16,10 +16,18 @@ package cron4s +import cron4s.parser.CronExpr + package object parsing { - private[cron4s] def parse(e: String): Either[Error, CronExpr] = - for { - tokens <- CronLexer.tokenize(e) - expr <- CronParser.read(tokens) - } yield expr + + object Parser extends cron4s.parser.Parser { + + override def parse(input: String): Either[parser.Error, CronExpr] = + for { + tokens <- CronLexer.tokenize(input) + expr <- CronParser.read(tokens) + } yield expr + + } + } diff --git a/modules/parserc/shared/src/main/scala/cron4s/parsing/parser.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/parser.scala index 5f605164..b972b628 100644 --- a/modules/parserc/shared/src/main/scala/cron4s/parsing/parser.scala +++ b/modules/parserc/shared/src/main/scala/cron4s/parsing/parser.scala @@ -17,7 +17,7 @@ package cron4s package parsing -import cron4s.expr._ +import cron4s.parser._ import scala.util.parsing.input._ import scala.util.parsing.combinator._ @@ -31,8 +31,7 @@ class CronTokenReader(tokens: List[CronToken]) extends Reader[CronToken] { } object CronParser extends Parsers with BaseParser { - import CronField._ - import CronUnit._ + import Node._ import CronToken._ override type Elem = CronToken @@ -55,115 +54,94 @@ object CronParser extends Parsers with BaseParser { // Seconds - val seconds: Parser[ConstNode[Second]] = - sexagesimal.map(ConstNode[Second](_)) + val seconds: Parser[ConstNode] = sexagesimal.map(ConstNode(_)) // Minutes - val minutes: Parser[ConstNode[Minute]] = - sexagesimal.map(ConstNode[Minute](_)) + val minutes: Parser[ConstNode] = sexagesimal.map(ConstNode(_)) // Hours - val hours: Parser[ConstNode[Hour]] = - decimal.filter(x => (x >= 0) && (x < 24)).map(ConstNode[Hour](_)) + val hours: Parser[ConstNode] = decimal.filter(x => (x >= 0) && (x < 24)).map(ConstNode(_)) // Days Of Month - val daysOfMonth: Parser[ConstNode[DayOfMonth]] = - decimal.filter(x => (x >= 1) && (x <= 31)).map(ConstNode[DayOfMonth](_)) + val daysOfMonth: Parser[ConstNode] = decimal.filter(x => (x >= 1) && (x <= 31)).map(ConstNode(_)) // Months - private[this] val numericMonths = - decimal.filter(_ <= 12).map(ConstNode[Month](_)) + private[this] val numericMonths = decimal.filter(_ <= 12).map(ConstNode(_)) private[this] val textualMonths = literal.filter(Months.textValues.contains).map { value => val index = Months.textValues.indexOf(value) - ConstNode[Month](index + 1, Some(value)) + ConstNode(index + 1, Some(value)) } - val months: Parser[ConstNode[Month]] = + val months: Parser[ConstNode] = textualMonths | numericMonths // Days Of Week private[this] val numericDaysOfWeek = - decimal.filter(_ < 7).map(ConstNode[DayOfWeek](_)) + decimal.filter(_ < 7).map(ConstNode(_)) private[this] val textualDaysOfWeek = literal.filter(DaysOfWeek.textValues.contains).map { value => val index = DaysOfWeek.textValues.indexOf(value) - ConstNode[DayOfWeek](index, Some(value)) + ConstNode(index, Some(value)) } - val daysOfWeek: Parser[ConstNode[DayOfWeek]] = + val daysOfWeek: Parser[ConstNode] = textualDaysOfWeek | numericDaysOfWeek // ---------------------------------------- // Field-Based Expression Atoms // ---------------------------------------- - def each[F <: CronField](implicit unit: CronUnit[F]): Parser[EachNode[F]] = - accept("*", { case Asterisk => EachNode[F] }) + def each: Parser[EachNode.type] = accept("*", { case Asterisk => EachNode }) - def any[F <: CronField](implicit unit: CronUnit[F]): Parser[AnyNode[F]] = - accept("?", { case QuestionMark => AnyNode[F] }) + def any: Parser[AnyNode.type] = accept("?", { case QuestionMark => AnyNode }) - def between[F <: CronField](base: Parser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): Parser[BetweenNode[F]] = - ((base <~ Hyphen) ~ base) ^^ { case min ~ max => BetweenNode[F](min, max) } + def between(base: Parser[ConstNode]): Parser[BetweenNode] = + ((base <~ Hyphen) ~ base) ^^ { case min ~ max => BetweenNode(min, max) } - def several[F <: CronField](base: Parser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): Parser[SeveralNode[F]] = { - def compose(b: Parser[EnumerableNode[F]]) = + def several(base: Parser[ConstNode]): Parser[SeveralNode] = { + def compose(b: Parser[EnumerableNode]) = repsep(b, Comma) .filter(_.length > 1) - .map(values => SeveralNode.fromSeq[F](values).get) + .map(values => SeveralNode.fromSeq(values).get) - compose(between(base).map(between2Enumerable) | base.map(const2Enumerable)) + compose(between(base) | base) } - def every[F <: CronField](base: Parser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): Parser[EveryNode[F]] = { - def compose(b: Parser[DivisibleNode[F]]) = + def every(base: Parser[ConstNode]): Parser[EveryNode] = { + def compose(b: Parser[DivisibleNode]) = ((b <~ Slash) ~ decimal.filter(_ > 0)) ^^ { - case exp ~ freq => EveryNode[F](exp, freq) + case exp ~ freq => EveryNode(exp, freq) } - compose( - several(base).map(several2Divisible) | - between(base).map(between2Divisible) | - each[F].map(each2Divisible) - ) + compose(several(base) | between(base) | each) } // ---------------------------------------- // AST Parsing & Building // ---------------------------------------- - def field[F <: CronField](base: Parser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): Parser[FieldNode[F]] = - every(base).map(every2Field) | - several(base).map(several2Field) | - between(base).map(between2Field) | - base.map(const2Field) | - each[F].map(each2Field) - - def fieldWithAny[F <: CronField](base: Parser[ConstNode[F]])(implicit - unit: CronUnit[F] - ): Parser[FieldNodeWithAny[F]] = - every(base).map(every2FieldWithAny) | - several(base).map(several2FieldWithAny) | - between(base).map(between2FieldWithAny) | - base.map(const2FieldWithAny) | - each[F].map(each2FieldWithAny) | - any[F].map(any2FieldWithAny) + def field(base: Parser[ConstNode]): Parser[NodeWithoutAny] = + every(base) | + several(base) | + between(base) | + base | + each + + def fieldWithAny(base: Parser[ConstNode]): Parser[Node] = + every(base) | + several(base) | + between(base) | + base | + each | + any val cron: Parser[CronExpr] = { phrase( @@ -179,7 +157,7 @@ object CronParser extends Parsers with BaseParser { } } - def read(tokens: List[CronToken]): Either[_root_.cron4s.Error, CronExpr] = { + def read(tokens: List[CronToken]): Either[_root_.cron4s.parser.Error, CronExpr] = { val reader = new CronTokenReader(tokens) cron(reader) match { case err: NoSuccess => Left(handleError(err)) diff --git a/tests/js/src/test/scala/cron4s/JsCronSpec.scala b/tests/js/src/test/scala/cron4s/JsCronSpec.scala index ee1e3bfe..4528c235 100644 --- a/tests/js/src/test/scala/cron4s/JsCronSpec.scala +++ b/tests/js/src/test/scala/cron4s/JsCronSpec.scala @@ -20,11 +20,11 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { - def parser = parsing.parse + def parser = parsing.Parser } class AttoCronSpec extends AnyFlatSpec with CronSpec { - def parser = atto.parser + def parser = atto.Parser } class CronParserComparisonSpec extends AnyFlatSpec with Matchers { @@ -32,7 +32,7 @@ class CronParserComparisonSpec extends AnyFlatSpec with Matchers { "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { forAll(CronSpec.validExpressions) { expr => - parsing.parse(expr) shouldBe atto.parser(expr) + parsing.Parser.parse(expr) shouldBe atto.Parser.parse(expr) } } } diff --git a/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala b/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala index ee1e3bfe..4528c235 100644 --- a/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala +++ b/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala @@ -20,11 +20,11 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { - def parser = parsing.parse + def parser = parsing.Parser } class AttoCronSpec extends AnyFlatSpec with CronSpec { - def parser = atto.parser + def parser = atto.Parser } class CronParserComparisonSpec extends AnyFlatSpec with Matchers { @@ -32,7 +32,7 @@ class CronParserComparisonSpec extends AnyFlatSpec with Matchers { "Parser-Combinators and Atto parsers" should "parse valid expressions with the same result" in { forAll(CronSpec.validExpressions) { expr => - parsing.parse(expr) shouldBe atto.parser(expr) + parsing.Parser.parse(expr) shouldBe atto.Parser.parse(expr) } } } diff --git a/tests/native/src/test/scala/cron4s/NativeCronSpec.scala b/tests/native/src/test/scala/cron4s/NativeCronSpec.scala index 1c372228..0307a858 100644 --- a/tests/native/src/test/scala/cron4s/NativeCronSpec.scala +++ b/tests/native/src/test/scala/cron4s/NativeCronSpec.scala @@ -19,5 +19,5 @@ package cron4s import org.scalatest.flatspec.AnyFlatSpec class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { - def parser = parsing.parse + def parser = parsing.Parser } diff --git a/tests/shared/src/test/scala/cron4s/CronSpec.scala b/tests/shared/src/test/scala/cron4s/CronSpec.scala index 0988e224..7d7efa47 100644 --- a/tests/shared/src/test/scala/cron4s/CronSpec.scala +++ b/tests/shared/src/test/scala/cron4s/CronSpec.scala @@ -102,7 +102,7 @@ object CronSpec extends TableDrivenPropertyChecks { trait CronSpec extends Matchers { this: AnyFlatSpec => import CronSpec._ - def parser: cron4s.Parser + def parser: cron4s.parser.Parser def cron: CronImpl = new CronImpl(parser) "Cron" should "not parse an invalid expression" in { diff --git a/tests/shared/src/test/scala/cron4s/parsing/ParserSpec.scala b/tests/shared/src/test/scala/cron4s/parsing/ParserSpec.scala index 1052e9e4..718a8e45 100644 --- a/tests/shared/src/test/scala/cron4s/parsing/ParserSpec.scala +++ b/tests/shared/src/test/scala/cron4s/parsing/ParserSpec.scala @@ -17,24 +17,20 @@ package cron4s package parsing -import cron4s.expr._ +import cron4s.parser._ +import cron4s.parser.Node._ import cron4s.testkit.Cron4sPropSpec -import cron4s.testkit.gen.{ArbitraryEachNode, NodeGenerators} -import org.scalacheck.Gen import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks /** * Created by alonsodomin on 13/01/2016. */ -class ParserSpec - extends Cron4sPropSpec with ScalaCheckDrivenPropertyChecks with InputGenerators - with NodeGenerators with ArbitraryEachNode { - import CronField._ - import CronUnit._ +class ParserSpec extends Cron4sPropSpec with ScalaCheckDrivenPropertyChecks with InputGenerators { + import CronParser._ - def verifyParsed[F <: CronField, N <: Node[F]](parser: Parser[N], input: String)( + def verifyParsed[N <: Node](parser: Parser[N], input: String)( verify: N => Boolean ): Boolean = { def readField(tokens: List[CronToken]) = { @@ -50,42 +46,39 @@ class ParserSpec // Utility methods to help with type inference - def verifyConst[F <: CronField](parser: Parser[ConstNode[F]], input: String)( - verify: ConstNode[F] => Boolean + def verifyConst(parser: Parser[ConstNode], input: String)( + verify: ConstNode => Boolean ): Boolean = - verifyParsed[F, ConstNode[F]](parser, input)(verify) + verifyParsed[ConstNode](parser, input)(verify) - def verifyEach(parser: Parser[EachNode[CronField]], input: String): Boolean = - verifyParsed[CronField, EachNode[CronField]](parser, input)(_ => true) + def verifyEach(parser: Parser[EachNode.type], input: String): Boolean = + verifyParsed[EachNode.type](parser, input)(_ => true) - def verifyAny(parser: Parser[AnyNode[CronField]], input: String): Boolean = - verifyParsed[CronField, AnyNode[CronField]](parser, input)(_ => true) + def verifyAny(parser: Parser[AnyNode.type], input: String): Boolean = + verifyParsed[AnyNode.type](parser, input)(_ => true) - def verifyBetween[F <: CronField](parser: Parser[BetweenNode[F]], input: String)( - verify: BetweenNode[F] => Boolean + def verifyBetween(parser: Parser[BetweenNode], input: String)( + verify: BetweenNode => Boolean ): Boolean = - verifyParsed[F, BetweenNode[F]](parser, input)(verify) + verifyParsed[BetweenNode](parser, input)(verify) - def verifySeveral[F <: CronField, A]( - parser: Parser[SeveralNode[F]], + def verifySeveral[A]( + parser: Parser[SeveralNode], input: String, expected: List[Either[String, (A, A)]] )( - verify: (ConstNode[F], String) => Boolean + verify: (ConstNode, String) => Boolean ): Boolean = - verifyParsed[F, SeveralNode[F]](parser, input) { expr => + verifyParsed[SeveralNode](parser, input) { expr => if (expr.values.toList.size == expected.size) { val matches = expr.values.toList.zip(expected).map { - case (exprPart, expectedPart) => - expectedPart match { - case Left(value) => - exprPart.raw.select[ConstNode[F]].exists(verify(_, value)) - - case Right((start, end)) => - exprPart.raw.select[BetweenNode[F]].exists { part => - verify(part.begin, start.toString) && verify(part.end, end.toString) - } - } + + case (part: ConstNode, Left(value)) => verify(part, value) + + case (part: BetweenNode, Right((start, end))) => + verify(part.begin, start.toString) && verify(part.end, end.toString) + case _ => false + } !matches.contains(false) @@ -96,21 +89,12 @@ class ParserSpec // Properties for the individual parsers // -------------------------------------------------------------- - val eachParserGen: Gen[Parser[EachNode[CronField]]] = - Gen.oneOf(CronUnit.All.map(each(_).asInstanceOf[Parser[EachNode[CronField]]])) - - property("should be able to parse an asterisk in any field") { - forAll(eachParserGen)(parser => verifyEach(parser, "*")) + property("should be able to parse an asterisk as each") { + verifyEach(CronParser.each, "*") } - val anyParserGen: Gen[Parser[AnyNode[CronField]]] = - Gen.oneOf( - Seq(any[DayOfMonth], any[DayOfWeek]) - .map(_.asInstanceOf[Parser[AnyNode[CronField]]]) - ) - - property("should be able to parse a question mark in any field") { - forAll(anyParserGen)(parser => verifyAny(parser, "?")) + property("should be able to parse a question mark as any") { + verifyAny(CronParser.any, "?") } property("should be able to parse seconds") { @@ -137,7 +121,7 @@ class ParserSpec property("should be able to parse named months") { forAll(nameMonthsGen) { x => verifyConst(months, x) { expr => - expr.textValue.contains(x) && expr.matches(Months.textValues.indexOf(x) + 1) + expr.textValue.contains(x) && expr.value == (Months.textValues.indexOf(x) + 1) } } } @@ -148,7 +132,7 @@ class ParserSpec property("should be able to parse named days of week") { forAll(namedDaysOfWeekGen) { x => verifyConst(daysOfWeek, x) { expr => - expr.textValue.contains(x) && expr.matches(DaysOfWeek.textValues.indexOf(x)) + expr.textValue.contains(x) && expr.value == (DaysOfWeek.textValues.indexOf(x)) } } } From 27af15ce3d32a81071d9b69dd4bd40d35d2fe8ca Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Fri, 17 Jan 2025 09:29:37 +0100 Subject: [PATCH 09/12] fix: mark parserc parser as deprecated --- bench/src/main/scala/cron4s/bench/ParserBenchmark.scala | 5 +++-- modules/core/native/src/main/scala/cron4s/Cron.scala | 2 ++ .../shared/src/main/scala/cron4s/parsing/package.scala | 1 + tests/js/src/test/scala/cron4s/JsCronSpec.scala | 4 ++++ tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala | 4 ++++ tests/native/src/test/scala/cron4s/NativeCronSpec.scala | 3 +++ 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala index 6e755e14..642401eb 100644 --- a/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala +++ b/bench/src/main/scala/cron4s/bench/ParserBenchmark.scala @@ -17,11 +17,11 @@ package cron4s.bench import java.util.concurrent.TimeUnit - import cron4s._ - import org.openjdk.jmh.annotations._ +import scala.annotation.nowarn + @State(Scope.Thread) @BenchmarkMode(Array(Mode.AverageTime)) @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -37,6 +37,7 @@ class ParserBenchmark { ) var cronString: String = _ + @nowarn("cat=deprecation") @Benchmark def parserCombinators() = parsing.Parser.parse(cronString) diff --git a/modules/core/native/src/main/scala/cron4s/Cron.scala b/modules/core/native/src/main/scala/cron4s/Cron.scala index b6e40d5f..c11ceb8a 100644 --- a/modules/core/native/src/main/scala/cron4s/Cron.scala +++ b/modules/core/native/src/main/scala/cron4s/Cron.scala @@ -16,6 +16,7 @@ package cron4s +import scala.annotation.nowarn import scala.scalajs.js.annotation.JSExportTopLevel /** @@ -24,4 +25,5 @@ import scala.scalajs.js.annotation.JSExportTopLevel * @author Antonio Alonso Dominguez */ @JSExportTopLevel("Cron") +@nowarn("cat=deprecation") object Cron extends CronImpl(parsing.Parser) diff --git a/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala b/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala index 5ef0e8d5..009d6210 100644 --- a/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala +++ b/modules/parserc/shared/src/main/scala/cron4s/parsing/package.scala @@ -20,6 +20,7 @@ import cron4s.parser.CronExpr package object parsing { + @deprecated(message = "Parser-Combinator parser in deprecated in favor of atto parser", since = "0.8.0") object Parser extends cron4s.parser.Parser { override def parse(input: String): Either[parser.Error, CronExpr] = diff --git a/tests/js/src/test/scala/cron4s/JsCronSpec.scala b/tests/js/src/test/scala/cron4s/JsCronSpec.scala index 4528c235..64734fa5 100644 --- a/tests/js/src/test/scala/cron4s/JsCronSpec.scala +++ b/tests/js/src/test/scala/cron4s/JsCronSpec.scala @@ -19,7 +19,10 @@ package cron4s import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import scala.annotation.nowarn + class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + @nowarn("cat=deprecation") def parser = parsing.Parser } @@ -27,6 +30,7 @@ class AttoCronSpec extends AnyFlatSpec with CronSpec { def parser = atto.Parser } +@nowarn("cat=deprecation") class CronParserComparisonSpec extends AnyFlatSpec with Matchers { import CronSpec._ diff --git a/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala b/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala index 4528c235..64734fa5 100644 --- a/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala +++ b/tests/jvm/src/test/scala/cron4s/JvmCronSpec.scala @@ -19,7 +19,10 @@ package cron4s import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import scala.annotation.nowarn + class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + @nowarn("cat=deprecation") def parser = parsing.Parser } @@ -27,6 +30,7 @@ class AttoCronSpec extends AnyFlatSpec with CronSpec { def parser = atto.Parser } +@nowarn("cat=deprecation") class CronParserComparisonSpec extends AnyFlatSpec with Matchers { import CronSpec._ diff --git a/tests/native/src/test/scala/cron4s/NativeCronSpec.scala b/tests/native/src/test/scala/cron4s/NativeCronSpec.scala index 0307a858..2d63abe6 100644 --- a/tests/native/src/test/scala/cron4s/NativeCronSpec.scala +++ b/tests/native/src/test/scala/cron4s/NativeCronSpec.scala @@ -18,6 +18,9 @@ package cron4s import org.scalatest.flatspec.AnyFlatSpec +import scala.annotation.nowarn + class ParserCombinatorsCronSpec extends AnyFlatSpec with CronSpec { + @nowarn("cat=deprecation") def parser = parsing.Parser } From 374c869451cf9891d6b4c63ad3e5be8b53cb9eec Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Fri, 17 Jan 2025 09:36:55 +0100 Subject: [PATCH 10/12] fix: ParserAdaptater can be package-private --- modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala b/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala index 62b42c01..01eedf80 100644 --- a/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala +++ b/modules/core/shared/src/main/scala/cron4s/ParserAdapter.scala @@ -16,7 +16,7 @@ package cron4s -object ParserAdapter { +private[cron4s] object ParserAdapter { import cron4s.parser._ import cron4s.parser.Node._ From c5d34b9ec4ef8de2d0b53a9e6fd2b6b0e4c2638d Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Fri, 17 Jan 2025 09:58:30 +0100 Subject: [PATCH 11/12] feat: provide a `withParser` method to make it obvious one can pick his parser of choice --- modules/core/js/src/main/scala/cron4s/Cron.scala | 8 +++++++- modules/core/jvm/src/main/scala/cron4s/Cron.scala | 8 +++++++- modules/core/native/src/main/scala/cron4s/Cron.scala | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/core/js/src/main/scala/cron4s/Cron.scala b/modules/core/js/src/main/scala/cron4s/Cron.scala index afcb1b8f..2f267f54 100644 --- a/modules/core/js/src/main/scala/cron4s/Cron.scala +++ b/modules/core/js/src/main/scala/cron4s/Cron.scala @@ -16,6 +16,8 @@ package cron4s +import cron4s.parser.Parser + import scala.scalajs.js.annotation.JSExportTopLevel /** @@ -24,4 +26,8 @@ import scala.scalajs.js.annotation.JSExportTopLevel * @author Antonio Alonso Dominguez */ @JSExportTopLevel("Cron") -object Cron extends CronImpl(atto.Parser) +object Cron extends CronImpl(atto.Parser) { + + def withParser(parser: Parser): CronImpl = new CronImpl(parser) + +} diff --git a/modules/core/jvm/src/main/scala/cron4s/Cron.scala b/modules/core/jvm/src/main/scala/cron4s/Cron.scala index afcb1b8f..2f267f54 100644 --- a/modules/core/jvm/src/main/scala/cron4s/Cron.scala +++ b/modules/core/jvm/src/main/scala/cron4s/Cron.scala @@ -16,6 +16,8 @@ package cron4s +import cron4s.parser.Parser + import scala.scalajs.js.annotation.JSExportTopLevel /** @@ -24,4 +26,8 @@ import scala.scalajs.js.annotation.JSExportTopLevel * @author Antonio Alonso Dominguez */ @JSExportTopLevel("Cron") -object Cron extends CronImpl(atto.Parser) +object Cron extends CronImpl(atto.Parser) { + + def withParser(parser: Parser): CronImpl = new CronImpl(parser) + +} diff --git a/modules/core/native/src/main/scala/cron4s/Cron.scala b/modules/core/native/src/main/scala/cron4s/Cron.scala index c11ceb8a..190c845f 100644 --- a/modules/core/native/src/main/scala/cron4s/Cron.scala +++ b/modules/core/native/src/main/scala/cron4s/Cron.scala @@ -16,6 +16,8 @@ package cron4s +import cron4s.parser.Parser + import scala.annotation.nowarn import scala.scalajs.js.annotation.JSExportTopLevel @@ -26,4 +28,8 @@ import scala.scalajs.js.annotation.JSExportTopLevel */ @JSExportTopLevel("Cron") @nowarn("cat=deprecation") -object Cron extends CronImpl(parsing.Parser) +object Cron extends CronImpl(parsing.Parser) { + + def withParser(parser: Parser): CronImpl = new CronImpl(parser) + +} From ea71d5294b5584ccee39983a081b09e81359a571 Mon Sep 17 00:00:00 2001 From: Matthieu Baechler Date: Fri, 17 Jan 2025 11:17:41 +0100 Subject: [PATCH 12/12] doc: explain how to use Parser Combinators if needed and library change --- CHANGELOG.md | 11 +++++++++++ docs/src/main/mdoc/userguide/ast.md | 4 ++-- docs/src/main/mdoc/userguide/index.md | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a843e56..412148e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Cron4s Change Log +## 0.8.0 + +Breaking changes: + +* In previous versions, Cron expression parsing was using [Scala Parser Combinators](https://github.com/scala/scala-parser-combinators). + Parsing is now achieved using [Atto library](https://tpolecat.github.io/atto/) by default on all targets except Native. + Both parsers should behave the same way and the API didn't change. They will be kept in sync for the time being. + In case you notice any change in behavior, please open an issue with your input. + You can always fall back to the Parser Combinator version by adding `cron4s-parserc` as a dependency of your project and + use `Cron.withParser(cron4s.parsing.Parser)` instead of `Cron` instance. + ## 0.6.1 Bug fixes diff --git a/docs/src/main/mdoc/userguide/ast.md b/docs/src/main/mdoc/userguide/ast.md index e54cd703..17b2d5a3 100644 --- a/docs/src/main/mdoc/userguide/ast.md +++ b/docs/src/main/mdoc/userguide/ast.md @@ -162,8 +162,8 @@ assert(minutesRange.implies(fixedMinute) == fixedMinute.impliedBy(minutesRange)) It's important to notice that when using either the `implies` or `impliedBy` operation, if the two nodes are not parameterized by the same field type, the code won't compile: - -```scala mdoc:fail + +```scala //mdoc:fail disabled because it breaks mdoc minutesRange.implies(eachSecond) ``` diff --git a/docs/src/main/mdoc/userguide/index.md b/docs/src/main/mdoc/userguide/index.md index b34c3be9..680f20c1 100644 --- a/docs/src/main/mdoc/userguide/index.md +++ b/docs/src/main/mdoc/userguide/index.md @@ -102,3 +102,26 @@ a `NonEmptyList` with all the validation errors that the expression had. To demo ```scala mdoc failsValidation.swap.foreach { err => err.asInstanceOf[InvalidCron].reason.toList.mkString("\n") } ``` + +## Using a specific parser implementation + +Cron4s has two parser implementation. Since 0.8.0, it uses the [Atto](https://tpolecat.github.io/atto/) implementation +by default for JS and JVM targets and [Scala Parser Combinators](https://github.com/scala/scala-parser-combinators) +for Native. + +You can choose to use the Parser Combinators version by importing cron4s-parserc + +```scala +libraryDependencies += "com.github.alonsodomin.cron4s" %%% "cron4s-parserc" % "{{site.cron4sVersion}}" +``` + +Then you need to specify the parser you want + +```scala mdoc:invisible +import scala.annotation.nowarn +``` + +```scala mdoc:silent +@nowarn("cat=deprecation") +val parser = Cron.withParser(cron4s.parsing.Parser) +``` \ No newline at end of file