Skip to content

Instantly share code, notes, and snippets.

@TonioGela
Last active July 19, 2024 10:31
Show Gist options
  • Save TonioGela/2a02b4c961df6d6fc8f814852668f574 to your computer and use it in GitHub Desktop.
Save TonioGela/2a02b4c961df6d6fc8f814852668f574 to your computer and use it in GitHub Desktop.
//> using scala 3.4.2
//> using platform native
//> using nativeVersion 0.4.17
//> using packaging.output checkLineLenght
//> using nativeMode debug
//> using nativeLto none
//> using nativeGc none
//> using toolkit typelevel::latest
import cats.syntax.all.*
import cats.effect.{ExitCode, IO, IOApp}
import cats.effect.std.Console
import fs2.Stream
import fs2.io.file.{Files, Path}
import com.monovore.decline.effect.CommandIOApp
import com.monovore.decline.Opts
import fs2.concurrent.SignallingRef
val header: String =
"Checks recursively for text file with a line length exceeding the passed value"
object checkLineLenght extends CommandIOApp("checkLineLength", header):
override def main: Opts[IO[ExitCode]] = Opts.argument[Long]("max-line-lenght").map(
check(_)(defaultFilters).asErrorCode
)
def check(maxLineLenght: Long)(filters: List[Filter]): IO[Boolean] =
for
cwd <- Files[IO].currentWorkingDirectory
success <- SignallingRef[IO, Boolean](true)
_ <- Files[IO]
.walk(cwd)
.filters(filters)
.map(cwd.relativize)
.readLineLength
.printIfLongerThan(maxLineLenght)(success)
.compile.drain
result <- success.get
yield result
end checkLineLenght
val defaultFilters: List[Filter] = List(
Filter.Negative(_.absolute.names.exists(_.toString === "target")),
Filter.Negative(_.absolute.names.exists(_.toString.startsWith("."))),
Filter.PositiveF(Files[IO].isRegularFile),
Filter.PositiveF(Files[IO].isReadable),
Filter.NegativeF(Files[IO].isExecutable),
)
extension (iob: IO[Boolean])
def asErrorCode: IO[ExitCode] = iob.ifF(ExitCode.Success, ExitCode.Error)
extension (s: Stream[IO, Path])
def filters(filters: List[Filter]): Stream[IO, Path] = filters.foldLeft(s):
case (s, Filter.Positive(f)) => s.filter(f)
case (s, Filter.Negative(f)) => s.filterNot(f)
case (s, Filter.PositiveF(f)) => s.evalFilterAsync(10)(f)
case (s, Filter.NegativeF(f)) => s.evalFilterNotAsync(10)(f)
def readLineLength: Stream[IO, (String, Int)] = s.flatMap: path =>
Files[IO].readUtf8Lines(path)
.zipWithIndex.map((s, i) => (s"${path.toString}:${i + 1}", s.length))
extension (s: Stream[IO, (String, Int)])
def printIfLongerThan(maxLineLenght: Long)(success: SignallingRef[IO, Boolean])
: Stream[IO, Unit] = s.parEvalMapUnorderedUnbounded((i, length) =>
Console[IO].errorln(i).flatMap(_ => success.set(false)).whenA(length > maxLineLenght)
)
enum Filter:
case Positive(f: Path => Boolean)
case Negative(f: Path => Boolean)
case PositiveF(f: Path => IO[Boolean])
case NegativeF(f: Path => IO[Boolean])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment