All Downloads are FREE. Search and download functionalities are using the official Maven repository.

laserdisc.fs2.CLI.scala Maven / Gradle / Ivy

There is a newer version: 0.2.3
Show newest version
package laserdisc
package fs2

import java.nio.channels.AsynchronousChannelGroup
import java.nio.channels.AsynchronousChannelGroup.withThreadPool
import java.util.concurrent.Executors.{newFixedThreadPool, newScheduledThreadPool}

import cats.effect._
import cats.syntax.all._
import _root_.fs2._
import _root_.fs2.Scheduler.fromScheduledExecutorService
import _root_.fs2.StreamApp.ExitCode

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Try

object CLI extends StreamApp[IO] {

  implicit val scheduler: Scheduler                        = fromScheduledExecutorService(newScheduledThreadPool(4))
  implicit val asyncChannelGroup: AsynchronousChannelGroup = withThreadPool(newFixedThreadPool(8))
  implicit val logger: Logger[IO] = new Logger[IO] {
    final def log(level: Logger.Level, msg: => String, maybeT: Logger.MaybeT): IO[Unit] = IO {
      println(f"[${level.value}%-5s]: $msg")
      maybeT.foreach(_.printStackTrace())
    }
  }

  override final def stream(args: List[String], requestShutdown: IO[Unit]): Stream[IO, ExitCode] = {
    val maybeHost = args.headOption.flatMap(Host.from(_).toOption)
    val maybePort = args.tail.headOption.flatMap(s => Try(s.toInt).toEither.flatMap(Port.from).toOption)
    (maybeHost, maybePort) match {
      case (Some(ip), Some(port)) => impl.mkStream[IO](ip, port)
      case _                      => Stream.emit(ExitCode.Error)
    }
  }

  private[fs2] final object impl {
    import scala.reflect.runtime.universe
    import scala.tools.reflect.ToolBox

    private val tb = universe.runtimeMirror(CLI.getClass.getClassLoader).mkToolBox()

    def mkStream[F[_]: Logger](host: Host, port: Port)(implicit F: Effect[F]): Stream[F, ExitCode] = {
      val promptStream: Stream[F, String] = Stream.emit(s"$host:$port> ").repeat

      val emptyPrompt: F[Unit] = promptStream.head.through(text.utf8Encode).to(io.stdout).compile.drain

      def prompt(msg: String): F[Unit] =
        Stream
          .emit(msg)
          .append(promptStream.head)
          .through(text.utf8Encode)
          .to(io.stdout)
          .compile
          .drain

      final object ToMillis {
        def unapply(nanos: Long): Option[Double] = Some(nanos.toDouble / 1000000d)
      }

      val toProtocol: Pipe[F, String, Protocol] = {
        def mkProtocol(code: String): F[Maybe[Protocol]] =
          F.delay {
            tb.eval {
                tb.parse {
                  s"""import laserdisc._
                     |import laserdisc.auto._
                     |import laserdisc.all._
                     |import shapeless._
                     |
                     |$code
                     |""".stripMargin
                }
              }
              .asInstanceOf[Protocol]
          }.attempt

        _.evalMap(mkProtocol).flatMap {
          case Left(t)         => Stream.eval_(prompt(s"${t.getLocalizedMessage}\n"))
          case Right(protocol) => Stream.emit(protocol)
        }
      }

      RedisClient(Set(RedisAddress(host, port))).evalMap { redisClient =>
        emptyPrompt.flatMap { _ =>
          io.stdin(10 * 1024)
            .through(text.utf8Decode)
            .through(text.lines)
            .through(toProtocol)
            .evalMap { protocol =>
              for {
                startTime             <- F.delay(System.nanoTime())
                maybeProtocolResponse <- redisClient.send1(protocol.asInstanceOf[Protocol.Aux[protocol.A]])
                endTime               <- F.delay(System.nanoTime())
              } yield maybeProtocolResponse.asInstanceOf[Maybe[Any]] -> (endTime - startTime)
            }
            .evalMap {
              case (Left(t), ToMillis(ms))         => prompt(f"<<< ERROR ${t.getLocalizedMessage} - [$ms%.2fms]\n")
              case (Right(response), ToMillis(ms)) => prompt(f"<<< $response - [$ms%.2fms]\n")
            }
            .compile
            .drain
            .map(_ => ExitCode.Success)
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy