diff --git a/build.sbt b/build.sbt index 7d327b9..a0ca72d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -val scala3: String = "3.4.2" +val scala3: String = "3.6.4" ThisBuild / scalaVersion := scala3 ThisBuild / versionScheme := Some("semver-spec") @@ -22,7 +22,7 @@ val sharedSettings = Seq( lazy val testSettings = Seq( libraryDependencies ++= Seq( - "org.scalameta" %% "munit" % "1.0.0" % Test + "org.scalameta" %% "munit" % "1.1.0" % Test ) ) @@ -39,6 +39,6 @@ lazy val core = project .settings(name := s"${gsProjectName.value}-core-v${semVerMajor.value}") .settings( libraryDependencies ++= Seq( - "gs" %% "gs-uuid-v0" % "0.3.0" + "gs" %% "gs-uuid-v0" % "0.4.0" ) ) diff --git a/modules/core/src/main/scala/gs/datagen/v0/Generated.scala b/modules/core/src/main/scala/gs/datagen/v0/Generated.scala index 29bd390..de4b54d 100644 --- a/modules/core/src/main/scala/gs/datagen/v0/Generated.scala +++ b/modules/core/src/main/scala/gs/datagen/v0/Generated.scala @@ -7,11 +7,23 @@ trait Generated[A]: object Generated: + /** Summon an instance of the [[Generated]] type class. + * + * @param G + * The type class instance. + * @return + * The summoned instance of [[Generated]]. + */ def apply[A]( using G: Generated[A] ): Generated[A] = G + /** Implementation of [[Generated]] based on an instance of [[Gen]]. + * + * @param gen + * The underlying [[Gen]] used to produce values. + */ final class FromGenerator[A](gen: Gen[A]) extends Generated[A]: override def generate(): A = gen.gen() diff --git a/modules/core/src/main/scala/gs/datagen/v0/gen.scala b/modules/core/src/main/scala/gs/datagen/v0/gen.scala index e097342..677be27 100644 --- a/modules/core/src/main/scala/gs/datagen/v0/gen.scala +++ b/modules/core/src/main/scala/gs/datagen/v0/gen.scala @@ -145,11 +145,11 @@ object Gen: end option - /** Generator for a list of some [[Size]] based on a generator for the list - * elements. + /** Generator for a list of some [[gs.datagen.v0.generators.Size]] based on a + * generator for the list elements. * * @param size - * The [[Size]] of the list. + * The [[gs.datagen.v0.generators.Size]] of the list. * @param gen * The generator for the list elements. */ @@ -158,12 +158,12 @@ object Gen: gen: Gen[A] ): Gen[List[A]] = new GenList[A](size, gen) - /** Generator for a set of some [[Size]] based on a generator for the set - * elements. + /** Generator for a set of some [[gs.datagen.v0.generators.Size]] based on a + * generator for the set elements. * * @param size - * The goal [[Size]] of the set. If duplicate elements are generated, the - * size will be less then specified. + * The goal [[gs.datagen.v0.generators.Size]] of the set. If duplicate + * elements are generated, the size will be less then specified. * @param gen * The generator for the list elements. */ @@ -234,7 +234,7 @@ object Gen: * values. * * @param size - * The [[Size]] of the generated map. + * The [[gs.datagen.v0.generators.Size]] of the generated map. * @param keyGen * The generator for keys. * @param valueGen @@ -255,7 +255,7 @@ object Gen: * keys to values. * * @param size - * The [[Size]] of the generated map. + * The [[gs.datagen.v0.generators.Size]] of the generated map. * @param keyValueGen * The generator for key/value pairs. */ @@ -274,8 +274,9 @@ object Gen: */ object string: - /** Generator for a string of the specified [[Size]], where the characters - * are restricted by the given alphabet. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * by the given alphabet. * * @param size * The size constraints for the generated string. @@ -291,8 +292,9 @@ object Gen: size = size ) - /** Generator for a string of the specified [[Size]], where the characters - * are restricted to ASCII lowercase letters. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * to ASCII lowercase letters. * * @param size * The size constraints for the generated string. @@ -305,8 +307,9 @@ object Gen: size = size ) - /** Generator for a string of the specified [[Size]], where the characters - * are restricted to ASCII uppercase letters. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * to ASCII uppercase letters. * * @param size * The size constraints for the generated string. @@ -319,8 +322,9 @@ object Gen: size = size ) - /** Generator for a string of the specified [[Size]], where the characters - * are restricted to ASCII letters. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * to ASCII letters. * * @param size * The size constraints for the generated string. @@ -333,8 +337,9 @@ object Gen: size = size ) - /** Generator for a string of the specified [[Size]], where the characters - * are restricted to ASCII letters and numbers. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * to ASCII letters and numbers. * * @param size * The size constraints for the generated string. @@ -347,8 +352,9 @@ object Gen: size = size ) - /** Generator for a string of the specified [[Size]], where the characters - * are restricted to lowercase ASCII letters and numbers. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * to lowercase ASCII letters and numbers. * * @param size * The size constraints for the generated string. @@ -361,8 +367,9 @@ object Gen: size = size ) - /** Generator for a string of the specified [[Size]], where the characters - * are restricted to uppercase ASCII letters and numbers. + /** Generator for a string of the specified + * [[gs.datagen.v0.generators.Size]], where the characters are restricted + * to uppercase ASCII letters and numbers. * * @param size * The size constraints for the generated string. @@ -377,7 +384,7 @@ object Gen: end string - /** Generators for integers. + /** Generators for integers (32-bit integers). */ object integer: @@ -411,7 +418,7 @@ object Gen: end integer - /** Generators for longs. + /** Generators for longs (64-bit integers). */ object long: @@ -453,7 +460,7 @@ object Gen: * * ## Example * - * The following generator produces a date before today where: + * The following generator produces a date before the given date where: * * - The day component is subtracted by 0 to 10. * - The month component is subtracted by 1 to 11. @@ -494,6 +501,37 @@ object Gen: years = years ) + /** Generate a date before the current date. + * + * ## Example + * + * The following generator produces a date before **today** where: + * + * - The day component is subtracted by 0 to 10. + * - The month component is subtracted by 1 to 11. + * - The year component is subtracted by 0 to 30. + * + * The produced date will be, at most, 30 years, 11 months and 10 days + * prior to the selected pivot. It will be at least 1 month prior to the + * selected pivot. + * + * {{{ + * Gen.date.beforeToday( + * days = MinMax.nonNegative(0, 10), + * months = MinMax.nonNegative(1, 11), + * years = MinMax.nonNegative(0, 30) + * ) + * }}} + * + * @param date + * The maximum date. + * @param days + * The range of days prior to today. + * @param months + * The range of months prior to today. + * @param years + * The range of years prior to today. + */ def beforeToday( days: MinMax.NonNegative, months: MinMax.NonNegative = MinMax.Zero, @@ -507,6 +545,38 @@ object Gen: years = years ) + /** Generate a date after the given date. + * + * ## Example + * + * The following generator produces a date after the given date where: + * + * - The day component is added by 0 to 10. + * - The month component is added by 1 to 11. + * - The year component is added by 0 to 30. + * + * The produced date will be, at most, 30 years, 11 months and 10 days + * after the selected pivot. It will be at least 1 month after the selected + * pivot. + * + * {{{ + * Gen.date.after( + * date = LocalDate.now(), + * days = MinMax.nonNegative(0, 10), + * months = MinMax.nonNegative(1, 11), + * years = MinMax.nonNegative(0, 30) + * ) + * }}} + * + * @param date + * The maximum date. + * @param days + * The range of days after the given date. + * @param months + * The range of months after the given date. + * @param years + * The range of years after the given date. + */ def after( date: LocalDate, days: MinMax.NonNegative, @@ -520,6 +590,38 @@ object Gen: years = years ) + /** Generate a date after the current date. + * + * ## Example + * + * The following generator produces a date after the **current** date + * where: + * + * - The day component is added by 0 to 10. + * - The month component is added by 1 to 11. + * - The year component is added by 0 to 30. + * + * The produced date will be, at most, 30 years, 11 months and 10 days + * after the selected pivot. It will be at least 1 month after the selected + * pivot. + * + * {{{ + * Gen.date.afterToday( + * days = MinMax.nonNegative(0, 10), + * months = MinMax.nonNegative(1, 11), + * years = MinMax.nonNegative(0, 30) + * ) + * }}} + * + * @param date + * The maximum date. + * @param days + * The range of days after today. + * @param months + * The range of months after today. + * @param years + * The range of years after today. + */ def afterToday( days: MinMax.NonNegative, months: MinMax.NonNegative = MinMax.Zero, @@ -533,26 +635,89 @@ object Gen: years = years ) + /** Generate a date around (centered upon) the given date. + * + * ## Example + * + * The following generator produces a date around the given date where: + * + * - The day component is added or subtracted by 0 to 10. + * - The month component is added or subtracted by 1 to 11. + * - The year component is added or subtracted by 0 to 30. + * + * The produced date will be, at most, 30 years, 11 months and 10 days + * before or after the selected pivot. It will be at least 1 month before + * or after the selected pivot. + * + * {{{ + * Gen.date.around( + * date = LocalDate.now(), + * days = MinMax.nonNegative(0, 10), + * months = MinMax.nonNegative(1, 11), + * years = MinMax.nonNegative(0, 30) + * ) + * }}} + * + * @param date + * The maximum date. + * @param days + * The range of days before or after the given date. + * @param months + * The range of months before or after the given date. + * @param years + * The range of years before or after the given date. + */ def around( date: LocalDate, days: MinMax, months: MinMax = MinMax.Zero, years: MinMax = MinMax.Zero ): Gen[LocalDate] = - new GenLocalDate.After( + new GenLocalDate.Around( pivot = date, days = days, months = months, years = years ) + /** Generate a date around (centered upon) today. + * + * ## Example + * + * The following generator produces a date around today where: + * + * - The day component is added or subtracted by 0 to 10. + * - The month component is added or subtracted by 1 to 11. + * - The year component is added or subtracted by 0 to 30. + * + * The produced date will be, at most, 30 years, 11 months and 10 days + * before or after today. It will be at least 1 month before or after + * today. + * + * {{{ + * Gen.date.around( + * days = MinMax.nonNegative(0, 10), + * months = MinMax.nonNegative(1, 11), + * years = MinMax.nonNegative(0, 30) + * ) + * }}} + * + * @param date + * The maximum date. + * @param days + * The range of days before or after today. + * @param months + * The range of months before or after today. + * @param years + * The range of years before or after today. + */ def aroundToday( days: MinMax, months: MinMax = MinMax.Zero, years: MinMax = MinMax.Zero, clock: Clock = DefaultClock ): Gen[LocalDate] = - new GenLocalDate.After( + new GenLocalDate.Around( pivot = LocalDate.now(clock), days = days, months = months, diff --git a/modules/core/src/main/scala/gs/datagen/v0/generators/GenLocalDate.scala b/modules/core/src/main/scala/gs/datagen/v0/generators/GenLocalDate.scala index d3073c4..bfcf196 100644 --- a/modules/core/src/main/scala/gs/datagen/v0/generators/GenLocalDate.scala +++ b/modules/core/src/main/scala/gs/datagen/v0/generators/GenLocalDate.scala @@ -2,11 +2,28 @@ package gs.datagen.v0.generators import gs.datagen.v0.Gen import java.time.LocalDate +import java.util.Random +/** Base for generators that produce `java.time.LocalDate` values. + */ trait GenLocalDate extends Gen[LocalDate] +/** Provids implementations for [[GenLocalDate]]. + */ object GenLocalDate: + /** Implementation of [[GenLocalDate]] that selects random dates before some + * given date. + * + * @param pivot + * The pivot date before which random values are selected. + * @param days + * The day bound. + * @param months + * The month bound. + * @param years + * The year bound. + */ final class Before( val pivot: LocalDate, val days: MinMax, @@ -14,6 +31,8 @@ object GenLocalDate: val years: MinMax ) extends GenLocalDate: + /** @inheritDocs + */ override def generate(input: Any): LocalDate = val rng = Gen.rng() pivot @@ -21,6 +40,18 @@ object GenLocalDate: .minusMonths(months.select(rng).toInt) .minusYears(years.select(rng).toInt) + /** Implementation of [[GenLocalDate]] that selects random dates after some + * given date. + * + * @param pivot + * The pivot date after which random values are selected. + * @param days + * The day bound. + * @param months + * The month bound. + * @param years + * The year bound. + */ final class After( val pivot: LocalDate, val days: MinMax, @@ -28,6 +59,8 @@ object GenLocalDate: val years: MinMax ) extends GenLocalDate: + /** @inheritDocs + */ override def generate(input: Any): LocalDate = val rng = Gen.rng() pivot @@ -35,4 +68,82 @@ object GenLocalDate: .plusMonths(months.select(rng).toInt) .plusYears(years.select(rng).toInt) + /** Implementation of [[GenLocalDate]] that selects random dates centered on + * some given date. + * + * @param pivot + * The pivot date around which random values are selected. + * @param days + * The day bound. + * @param months + * The month bound. + * @param years + * The year bound. + */ + final class Around( + val pivot: LocalDate, + val days: MinMax, + val months: MinMax, + val years: MinMax + ) extends GenLocalDate: + + /** @inheritDocs + */ + override def generate(input: Any): LocalDate = + val rng = Gen.rng() + pivot + .plusOrMinusDays(rng, days) + .plusOrMinusMonths(rng, months) + .plusOrMinusYears(rng, years) + + extension (base: LocalDate) + + /** Select a bounded random number of days before or after the base date. + * + * @param rng + * The random generator. + * @param range + * The bounded range. + * @return + * The new date. + */ + def plusOrMinusDays( + rng: Random, + range: MinMax + ): LocalDate = + if rng.nextBoolean() then base.plusDays(range.select(rng).toInt) + else base.minusDays(range.select(rng).toInt) + + /** Select a bounded random number of months before or after the base date. + * + * @param rng + * The random generator. + * @param range + * The bounded range. + * @return + * The new date. + */ + def plusOrMinusMonths( + rng: Random, + range: MinMax + ): LocalDate = + if rng.nextBoolean() then base.plusMonths(range.select(rng).toInt) + else base.minusMonths(range.select(rng).toInt) + + /** Select a bounded random number of years before or after the base date. + * + * @param rng + * The random generator. + * @param range + * The bounded range. + * @return + * The new date. + */ + def plusOrMinusYears( + rng: Random, + range: MinMax + ): LocalDate = + if rng.nextBoolean() then base.plusYears(range.select(rng).toInt) + else base.minusYears(range.select(rng).toInt) + end GenLocalDate diff --git a/modules/core/src/main/scala/gs/datagen/v0/generators/GenUUID.scala b/modules/core/src/main/scala/gs/datagen/v0/generators/GenUUID.scala index 6377224..0014812 100644 --- a/modules/core/src/main/scala/gs/datagen/v0/generators/GenUUID.scala +++ b/modules/core/src/main/scala/gs/datagen/v0/generators/GenUUID.scala @@ -4,7 +4,7 @@ import gs.datagen.v0.Gen import gs.uuid.v0.UUID final class GenUUID extends Gen[UUID]: - given UUID.Generator = UUID.Generator.version4() + given UUID.Generator = UUID.Generator.version4 override def generate(input: Any): UUID = UUID.generate() diff --git a/project/build.properties b/project/build.properties index ee4c672..cc68b53 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.1 +sbt.version=1.10.11 diff --git a/project/plugins.sbt b/project/plugins.sbt index 8830bf1..cac78e0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -28,6 +28,6 @@ externalResolvers := Seq( "Garrity Software Releases" at "https://maven.garrity.co/gs" ) -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12") -addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1") +addSbtPlugin("gs" % "sbt-garrity-software" % "0.5.0") addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")