Expanding library to effectful operations.
All checks were successful
/ Build and Release Library (push) Successful in 1m31s

This commit is contained in:
Pat Garrity 2026-04-30 22:06:35 -05:00
parent 9abef2d8ce
commit eedf6e8f04
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
3 changed files with 289 additions and 9 deletions

View file

@ -45,7 +45,7 @@ lazy val testSettings = Seq(
lazy val `gs-std` = project lazy val `gs-std` = project
.in(file(".")) .in(file("."))
.aggregate(core, io, effect, stream) .aggregate(core, io, effect)
.settings(sharedSettings) .settings(sharedSettings)
.settings(testSettings) .settings(testSettings)
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}") .settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
@ -75,11 +75,6 @@ lazy val effect = project
.settings(sharedSettings) .settings(sharedSettings)
.settings(testSettings) .settings(testSettings)
.settings(name := s"${gsProjectName.value}-effect-v${semVerMajor.value}") .settings(name := s"${gsProjectName.value}-effect-v${semVerMajor.value}")
.settings(libraryDependencies ++= Seq(Deps.Cats.Core, Deps.Cats.Effect)) .settings(
libraryDependencies ++= Seq(Deps.Cats.Core, Deps.Cats.Effect, Deps.Fs2.Core)
lazy val stream = project )
.in(file("modules/stream"))
.settings(sharedSettings)
.settings(testSettings)
.settings(name := s"${gsProjectName.value}-stream-v${semVerMajor.value}")
.settings(libraryDependencies ++= Seq(Deps.Fs2.Core))

View file

@ -0,0 +1,168 @@
package gs.std.v0.effect
import cats.Applicative
import cats.effect.Sync
import fs2.Stream
import java.security.SecureRandom
import java.util.Random
trait RNG[F[_]]:
def nextInt(): F[Int]
def nextInt(bound: Int): F[Int]
def nextInt(
origin: Int,
bound: Int
): F[Int]
def nextLong(): F[Long]
def nextLong(bound: Long): F[Long]
def nextLong(
origin: Long,
bound: Long
): F[Long]
def nextDouble(): F[Double]
def nextDouble(bound: Double): F[Double]
def nextDouble(
origin: Double,
bound: Double
): F[Double]
def nextBoolean(): F[Boolean]
def nextInts(
origin: Int,
bound: Int
): Stream[F, Int]
def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long]
def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double]
object RNG:
def secure[F[_]: Sync]: RNG[F] =
new JavaRandom[F](new SecureRandom())
final class JavaRandom[F[_]: Sync](random: Random) extends RNG[F]:
override def nextInt(): F[Int] =
Sync[F].delay(random.nextInt())
override def nextInt(bound: Int): F[Int] =
Sync[F].delay(random.nextInt(bound))
override def nextInt(
origin: Int,
bound: Int
): F[Int] =
Sync[F].delay(random.nextInt(origin, bound))
override def nextLong(): F[Long] =
Sync[F].delay(random.nextLong())
override def nextLong(bound: Long): F[Long] =
Sync[F].delay(random.nextLong(bound))
override def nextLong(
origin: Long,
bound: Long
): F[Long] =
Sync[F].delay(random.nextLong(origin, bound))
override def nextDouble(): F[Double] =
Sync[F].delay(random.nextDouble())
override def nextDouble(bound: Double): F[Double] =
Sync[F].delay(random.nextDouble(bound))
override def nextDouble(
origin: Double,
bound: Double
): F[Double] =
Sync[F].delay(random.nextDouble(origin, bound))
override def nextBoolean(): F[Boolean] =
Sync[F].delay(random.nextBoolean())
override def nextInts(
origin: Int,
bound: Int
): Stream[F, Int] =
Stream.repeatEval(nextInt(origin, bound))
override def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long] =
Stream.repeatEval(nextLong(origin, bound))
override def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double] =
Stream.repeatEval(nextDouble(origin, bound))
end JavaRandom
final class Zero[F[_]: Applicative] extends RNG[F]:
override def nextInt(): F[Int] = Applicative[F].pure(0)
override def nextInt(bound: Int): F[Int] = Applicative[F].pure(0)
override def nextInt(
origin: Int,
bound: Int
): F[Int] = Applicative[F].pure(0)
override def nextLong(): F[Long] = Applicative[F].pure(0)
override def nextLong(bound: Long): F[Long] = Applicative[F].pure(0)
override def nextLong(
origin: Long,
bound: Long
): F[Long] = Applicative[F].pure(0)
override def nextDouble(): F[Double] = Applicative[F].pure(0.0)
override def nextDouble(bound: Double): F[Double] = Applicative[F].pure(0.0)
override def nextDouble(
origin: Double,
bound: Double
): F[Double] = Applicative[F].pure(0.0)
override def nextBoolean(): F[Boolean] = Applicative[F].pure(false)
override def nextInts(
origin: Int,
bound: Int
): Stream[F, Int] =
Stream.repeatEval(Applicative[F].pure(0))
override def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long] =
Stream.repeatEval(Applicative[F].pure(0))
override def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double] =
Stream.repeatEval(Applicative[F].pure(0.0))
end Zero
end RNG

View file

@ -0,0 +1,117 @@
package gs.std.v0.io
import cats.effect.Resource
import cats.effect.Sync
import java.io.InputStream
import java.io.OutputStream
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import scala.jdk.CollectionConverters.*
/** Common operations on files, wrapped using Cats Effect. If you want to Stream
* files, the `fs2-io` package is the place to go.
*
* Includes wrappers around some `java.nio.file.Files` functionality.
*/
object Files:
def readFileAsString[F[_]: Sync](
path: Path,
charset: Charset = StandardCharsets.UTF_8
): F[String] =
Sync[F].delay(java.nio.file.Files.readString(path, charset))
def readFileAsBytes[F[_]: Sync](path: Path): F[Array[Byte]] =
Sync[F].delay(java.nio.file.Files.readAllBytes(path))
def readFileAsLines[F[_]: Sync](
path: Path,
charset: Charset = StandardCharsets.UTF_8
): F[List[String]] =
Sync[F].delay(
java.nio.file.Files.readAllLines(path, charset).asScala.toList
)
def overwriteFileWithString[F[_]: Sync](
path: Path,
data: String
): F[Unit] =
Sync[F].delay(
java.nio.file.Files.writeString(
path,
data,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING
)
)
def overwriteFileWithBytes[F[_]: Sync](
path: Path,
data: Array[Byte]
): F[Unit] =
Sync[F].delay(
java.nio.file.Files.write(
path,
data,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING
)
)
def appendFileWithString[F[_]: Sync](
path: Path,
data: String
): F[Unit] =
Sync[F].delay(
java.nio.file.Files.writeString(
path,
data,
StandardOpenOption.WRITE,
StandardOpenOption.APPEND
)
)
def appendFileWithBytes[F[_]: Sync](
path: Path,
data: Array[Byte]
): F[Unit] =
Sync[F].delay(
java.nio.file.Files
.write(path, data, StandardOpenOption.WRITE, StandardOpenOption.APPEND)
)
def isRegularFile[F[_]: Sync](path: Path): F[Boolean] =
Sync[F].delay(java.nio.file.Files.isRegularFile(path))
def isReadable[F[_]: Sync](path: Path): F[Boolean] =
Sync[F].delay(java.nio.file.Files.isReadable(path))
def isWritable[F[_]: Sync](path: Path): F[Boolean] =
Sync[F].delay(java.nio.file.Files.isWritable(path))
def isDirectory[F[_]: Sync](path: Path): F[Boolean] =
Sync[F].delay(java.nio.file.Files.isDirectory(path))
def isExecutable[F[_]: Sync](path: Path): F[Boolean] =
Sync[F].delay(java.nio.file.Files.isExecutable(path))
def openInputStream[F[_]: Sync](
path: Path,
openOptions: OpenOption*
): Resource[F, InputStream] =
Resource.make(
Sync[F].delay(java.nio.file.Files.newInputStream(path, openOptions*))
)(inputStream => Sync[F].delay(inputStream.close()))
def openOutputStream[F[_]: Sync](
path: Path,
openOptions: OpenOption*
): Resource[F, OutputStream] =
Resource.make(
Sync[F].delay(java.nio.file.Files.newOutputStream(path, openOptions*))
)(outputStream => Sync[F].delay(outputStream.close()))
end Files