Initial version -- ~45 minutes of poking around.
This commit is contained in:
commit
439c28c1c7
13 changed files with 310 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
target/
|
||||||
|
project/target/
|
||||||
|
project/project/
|
||||||
|
.version
|
||||||
16
.pre-commit-config.yaml
Normal file
16
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: fix-byte-order-marker
|
||||||
|
- id: mixed-line-ending
|
||||||
|
args: ['--fix=lf']
|
||||||
|
description: Enforces using only 'LF' line endings.
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala
|
||||||
|
rev: v1.0.1
|
||||||
|
hooks:
|
||||||
|
- id: scalafmt
|
||||||
72
.scalafmt.conf
Normal file
72
.scalafmt.conf
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
|
||||||
|
version = 3.8.1
|
||||||
|
runner.dialect = scala3
|
||||||
|
maxColumn = 80
|
||||||
|
|
||||||
|
rewrite {
|
||||||
|
rules = [RedundantBraces, RedundantParens, Imports, SortModifiers]
|
||||||
|
imports.expand = true
|
||||||
|
imports.sort = scalastyle
|
||||||
|
redundantBraces.ifElseExpressions = true
|
||||||
|
redundantBraces.stringInterpolation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
indent {
|
||||||
|
main = 2
|
||||||
|
callSite = 2
|
||||||
|
defnSite = 2
|
||||||
|
extendSite = 4
|
||||||
|
withSiteRelativeToExtends = 2
|
||||||
|
commaSiteRelativeToExtends = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
align {
|
||||||
|
preset = more
|
||||||
|
openParenCallSite = false
|
||||||
|
openParenDefnSite = false
|
||||||
|
}
|
||||||
|
|
||||||
|
newlines {
|
||||||
|
implicitParamListModifierForce = [before,after]
|
||||||
|
topLevelStatementBlankLines = [
|
||||||
|
{
|
||||||
|
blanks = 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
afterCurlyLambdaParams = squash
|
||||||
|
}
|
||||||
|
|
||||||
|
danglingParentheses {
|
||||||
|
defnSite = true
|
||||||
|
callSite = true
|
||||||
|
ctrlSite = true
|
||||||
|
exclude = []
|
||||||
|
}
|
||||||
|
|
||||||
|
verticalMultiline {
|
||||||
|
atDefnSite = true
|
||||||
|
arityThreshold = 2
|
||||||
|
newlineAfterOpenParen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
comments {
|
||||||
|
wrap = standalone
|
||||||
|
}
|
||||||
|
|
||||||
|
docstrings {
|
||||||
|
style = "SpaceAsterisk"
|
||||||
|
oneline = unfold
|
||||||
|
wrap = yes
|
||||||
|
forceBlankLineBefore = true
|
||||||
|
}
|
||||||
|
|
||||||
|
project {
|
||||||
|
excludePaths = [
|
||||||
|
"glob:**target/**",
|
||||||
|
"glob:**.metals/**",
|
||||||
|
"glob:**.bloop/**",
|
||||||
|
"glob:**.bsp/**",
|
||||||
|
"glob:**metals.sbt",
|
||||||
|
"glob:**.git/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# fizzbuzz
|
||||||
|
|
||||||
|
This project is an over-engineered fizzbuzz to buzz your fizz.
|
||||||
59
build.sbt
Normal file
59
build.sbt
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
val scala3: String = "3.8.4"
|
||||||
|
|
||||||
|
ThisBuild / scalaVersion := scala3
|
||||||
|
ThisBuild / gsProjectName := "fizzbuzz"
|
||||||
|
|
||||||
|
ThisBuild / externalResolvers := Seq(
|
||||||
|
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
|
||||||
|
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||||
|
)
|
||||||
|
|
||||||
|
lazy val sharedSettings = Seq(
|
||||||
|
scalaVersion := scala3,
|
||||||
|
version := calVer.value,
|
||||||
|
publish / skip := true,
|
||||||
|
publishLocal / skip := true,
|
||||||
|
publishArtifact := false
|
||||||
|
)
|
||||||
|
|
||||||
|
val Deps = new {
|
||||||
|
val Cats = new {
|
||||||
|
val Core: ModuleID = "org.typelevel" %% "cats-core" % "2.13.0"
|
||||||
|
val Effect: ModuleID = "org.typelevel" %% "cats-effect" % "3.7.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
val Fs2 = new {
|
||||||
|
private val Version: String = "3.13.0"
|
||||||
|
|
||||||
|
val Core: ModuleID = "co.fs2" %% "fs2-core" % Version
|
||||||
|
val IO: ModuleID = "co.fs2" %% "fs2-io" % Version
|
||||||
|
}
|
||||||
|
|
||||||
|
val Gs = new {
|
||||||
|
val Std: ModuleID = "gs" %% "gs-std-core-v0" % "0.1.3"
|
||||||
|
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.3.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val testSettings = Seq(
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
Deps.MUnit % Test,
|
||||||
|
Deps.Gs.Datagen % Test
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
lazy val fizzbuzz = project
|
||||||
|
.in(file("."))
|
||||||
|
.settings(sharedSettings)
|
||||||
|
.settings(testSettings)
|
||||||
|
.settings(name := s"${gsProjectName.value}")
|
||||||
|
.settings(fork := true)
|
||||||
|
.settings(
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
Deps.Cats.Core,
|
||||||
|
Deps.Cats.Effect,
|
||||||
|
Deps.Fs2.Core
|
||||||
|
)
|
||||||
|
)
|
||||||
1
project/build.properties
Normal file
1
project/build.properties
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
sbt.version=1.12.11
|
||||||
33
project/plugins.sbt
Normal file
33
project/plugins.sbt
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
def selectCredentials(): Credentials =
|
||||||
|
if ((Path.userHome / ".sbt" / ".credentials").exists())
|
||||||
|
Credentials(Path.userHome / ".sbt" / ".credentials")
|
||||||
|
else
|
||||||
|
Credentials.apply(
|
||||||
|
realm = "Reposilite",
|
||||||
|
host = "maven.garrity.co",
|
||||||
|
userName = sys.env
|
||||||
|
.get("GS_MAVEN_USER")
|
||||||
|
.getOrElse(
|
||||||
|
throw new RuntimeException(
|
||||||
|
"You must either provide ~/.sbt/.credentials or specify the GS_MAVEN_USER environment variable."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
passwd = sys.env
|
||||||
|
.get("GS_MAVEN_TOKEN")
|
||||||
|
.getOrElse(
|
||||||
|
throw new RuntimeException(
|
||||||
|
"You must either provide ~/.sbt/.credentials or specify the GS_MAVEN_TOKEN environment variable."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
credentials += selectCredentials()
|
||||||
|
|
||||||
|
externalResolvers := Seq(
|
||||||
|
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
|
||||||
|
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||||
|
)
|
||||||
|
|
||||||
|
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4")
|
||||||
|
addSbtPlugin("gs" % "sbt-garrity-software" % "0.7.0")
|
||||||
|
addSbtPlugin("gs" % "sbt-gs-calver" % "0.2.0")
|
||||||
18
src/main/scala/gs/fizzbuzz/Condition.scala
Normal file
18
src/main/scala/gs/fizzbuzz/Condition.scala
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package gs.fizzbuzz
|
||||||
|
|
||||||
|
import cats.Applicative
|
||||||
|
|
||||||
|
trait Condition[F[_], Input, Output]:
|
||||||
|
def matches(input: Input): F[Boolean]
|
||||||
|
def output: Output
|
||||||
|
end Condition
|
||||||
|
|
||||||
|
object Condition:
|
||||||
|
|
||||||
|
final class Noop[F[_]: Applicative, Input] extends Condition[F, Input, Nothing]:
|
||||||
|
override def matches(input: Input): F[Boolean] =
|
||||||
|
Applicative[F].pure(false)
|
||||||
|
override def output: Nothing =
|
||||||
|
throw new IllegalStateException("No-op condition cannot produce output.")
|
||||||
|
|
||||||
|
end Condition
|
||||||
15
src/main/scala/gs/fizzbuzz/DefaultCondition.scala
Normal file
15
src/main/scala/gs/fizzbuzz/DefaultCondition.scala
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package gs.fizzbuzz
|
||||||
|
|
||||||
|
import cats.Applicative
|
||||||
|
import cats.Monoid
|
||||||
|
|
||||||
|
final class DefaultCondition[F[_]: Applicative, Input, Out](
|
||||||
|
val comparison: Input,
|
||||||
|
val output: Out,
|
||||||
|
val operator: Monoid[Input]
|
||||||
|
)(using CanEqual[Input, Input]) extends Condition[F, Input, Out]:
|
||||||
|
|
||||||
|
override def matches(input: Input): F[Boolean] =
|
||||||
|
Applicative[F].pure(
|
||||||
|
operator.combine(input, comparison) == operator.empty
|
||||||
|
)
|
||||||
26
src/main/scala/gs/fizzbuzz/DefaultFizzBuzz.scala
Normal file
26
src/main/scala/gs/fizzbuzz/DefaultFizzBuzz.scala
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package gs.fizzbuzz
|
||||||
|
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import fs2.Pipe
|
||||||
|
import cats.FlatMap
|
||||||
|
|
||||||
|
class DefaultFizzBuzz[F[_]: FlatMap, Input, Out](
|
||||||
|
val fizzCondition: Condition[F, Input, Out],
|
||||||
|
val buzzCondition: Condition[F, Input, Out]
|
||||||
|
) extends FizzBuzz[F, Input, Out]:
|
||||||
|
def evaluate(input: Input): F[Result[Out]] =
|
||||||
|
for
|
||||||
|
fizz <- fizzCondition.matches(input)
|
||||||
|
buzz <- buzzCondition.matches(input)
|
||||||
|
yield
|
||||||
|
if fizz && buzz then
|
||||||
|
Result.FizzBuzz(fizzCondition.output, buzzCondition.output)
|
||||||
|
else if fizz then
|
||||||
|
Result.Fizz(fizzCondition.output)
|
||||||
|
else if buzz then
|
||||||
|
Result.Buzz(buzzCondition.output)
|
||||||
|
else
|
||||||
|
Result.Unmatched
|
||||||
|
|
||||||
|
def pipe(): Pipe[F, Input, Result[Out]] = in => in.evalMap(evaluate)
|
||||||
|
end DefaultFizzBuzz
|
||||||
8
src/main/scala/gs/fizzbuzz/FizzBuzz.scala
Normal file
8
src/main/scala/gs/fizzbuzz/FizzBuzz.scala
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package gs.fizzbuzz
|
||||||
|
|
||||||
|
import fs2.Pipe
|
||||||
|
|
||||||
|
trait FizzBuzz[F[_], Input, Out]:
|
||||||
|
def evaluate(input: Input): F[Result[Out]]
|
||||||
|
def pipe(): Pipe[F, Input, Result[Out]]
|
||||||
|
end FizzBuzz
|
||||||
42
src/main/scala/gs/fizzbuzz/Main.scala
Normal file
42
src/main/scala/gs/fizzbuzz/Main.scala
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package gs.fizzbuzz
|
||||||
|
|
||||||
|
import cats.Monoid
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.effect.IOApp
|
||||||
|
import cats.effect.ExitCode
|
||||||
|
|
||||||
|
object Main extends IOApp:
|
||||||
|
override def run(args: List[String]): IO[ExitCode] =
|
||||||
|
val op: Monoid[Long] = new Monoid[Long] {
|
||||||
|
override def empty: Long = 0L
|
||||||
|
override def combine(x: Long, y: Long): Long = x % y
|
||||||
|
}
|
||||||
|
|
||||||
|
val fizzCond: Condition[IO, Long, String] = new DefaultCondition(
|
||||||
|
comparison = 3L,
|
||||||
|
output = "Fizz",
|
||||||
|
operator = op
|
||||||
|
)
|
||||||
|
|
||||||
|
val buzzCond: Condition[IO, Long, String] = new DefaultCondition(
|
||||||
|
comparison = 5L,
|
||||||
|
output = "Buzz",
|
||||||
|
operator = op
|
||||||
|
)
|
||||||
|
|
||||||
|
val fb: FizzBuzz[IO, Long, String] = new DefaultFizzBuzz[IO, Long, String](
|
||||||
|
fizzCondition = fizzCond,
|
||||||
|
buzzCondition = buzzCond
|
||||||
|
)
|
||||||
|
|
||||||
|
for
|
||||||
|
ex1 <- fb.evaluate(15)
|
||||||
|
ex2 <- fb.evaluate(9)
|
||||||
|
ex3 <- fb.evaluate(20)
|
||||||
|
ex4 <- fb.evaluate(13)
|
||||||
|
_ <- IO.println(ex1)
|
||||||
|
_ <- IO.println(ex2)
|
||||||
|
_ <- IO.println(ex3)
|
||||||
|
_ <- IO.println(ex4)
|
||||||
|
yield
|
||||||
|
ExitCode.Success
|
||||||
13
src/main/scala/gs/fizzbuzz/Result.scala
Normal file
13
src/main/scala/gs/fizzbuzz/Result.scala
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package gs.fizzbuzz
|
||||||
|
|
||||||
|
sealed trait Result[+Out]
|
||||||
|
|
||||||
|
object Result:
|
||||||
|
|
||||||
|
case class Fizz[Out](fizz: Out) extends Result[Out]
|
||||||
|
case class Buzz[Out](buzz: Out) extends Result[Out]
|
||||||
|
case class FizzBuzz[Out](fizz: Out, buzz: Out) extends Result[Out]
|
||||||
|
case object Unmatched extends Result[Nothing]:
|
||||||
|
override def toString(): String = "unmatched"
|
||||||
|
|
||||||
|
end Result
|
||||||
Loading…
Add table
Reference in a new issue