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

quasar.repl.Main.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014–2017 SlamData Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package quasar.repl

import slamdata.Predef._
import quasar.build.BuildInfo
import quasar.config._
import quasar.console._
import quasar.contrib.scalaz._
import quasar.contrib.scopt._
import quasar.effect._
import quasar.fp._
import quasar.fp.free._
import quasar.fs._
import quasar.fs.mount._
import quasar.main._

import eu.timepit.refined.refineMV
import eu.timepit.refined.numeric.{NonNegative, Positive}
import org.jboss.aesh.console.{AeshConsoleCallback, Console, ConsoleOperation, Prompt}
import org.jboss.aesh.console.helper.InterruptHook
import org.jboss.aesh.console.settings.SettingsBuilder
import org.jboss.aesh.edit.actions.Action
import pathy.Path, Path._
import scalaz.{Failure => _, _}, Scalaz._
import scalaz.concurrent.Task

object Main {
  private def consoleIO(console: Console): ConsoleIO ~> Task =
    new (ConsoleIO ~> Task) {
      import ConsoleIO._

      def apply[A](c: ConsoleIO[A]): Task[A] = c match {
        case PrintLn(message) => Task.delay { console.getShell.out.println(message) }
      }
    }

  type DriverEff0[A] = Coproduct[ConsoleIO, Task, A]
  type DriverEff[A]  = Coproduct[ReplFail, DriverEff0, A]
  type DriverEffM[A] = Free[DriverEff, A]

  private def driver(f: Command => Free[DriverEff, Unit]): Task[Unit] = {
    val runConsole = Task.async[Console] { callback =>
      val console =
        new Console(new SettingsBuilder()
          .parseOperators(false)
          .enableExport(false)
          .interruptHook(new InterruptHook {
            def handleInterrupt(console: Console, action: Action) =
              callback(console.right)
          })
          .create())

      console.setPrompt(new Prompt(s"(v${BuildInfo.version}) 💪 $$ "))

      val i: DriverEff ~> MainTask =
        Failure.toError[MainTask, String]                  :+:
        liftMT[Task, MainErrT].compose(consoleIO(console)) :+:
        liftMT[Task, MainErrT]

      console.setConsoleCallback(new AeshConsoleCallback() {
        override def execute(input: ConsoleOperation): Int = {
          Command.parse(input.getBuffer.trim) match {
            case Command.Exit =>
              callback(console.right)

            case command      =>
              f(command).foldMap(i).run.unsafePerformSync.valueOr(
                err => console.getShell.out.println("Quasar error: " + err))
          }
          0
        }
      })

      console.start()

      ()
    }

    runConsole.flatMap(c => Task.delay(c.getShell.out.println("Exiting...")).onFinish(_ => Task.delay(c.stop)))
  }

  type ReplEff[S[_], A] = (
        Repl.RunStateT
    :\: ConsoleIO
    :\: ReplFail
    :\: Timing
    :\: Task
    :/: S
  )#M[A]

  def repl[S[_]](
    fs: S ~> DriverEffM
  )(implicit
    S0: Mounting :<: S,
    S1: QueryFile :<: S,
    S2: ReadFile :<: S,
    S3: WriteFile :<: S,
    S4: ManageFile :<: S,
    S5: FileSystemFailure :<: S
  ): Task[Command => Free[DriverEff, Unit]] = {
    for {
      stateRef <- TaskRef(Repl.RunState(rootDir, DebugLevel.Normal, PhaseFormat.Tree, refineMV[Positive](10).some, OutputFormat.Table, Map(), TimingFormat.OnlyTotal))
      executionIdRef <- TaskRef(0L)
      timingRepository <- TimingRepository.empty(refineMV[NonNegative](1L))
      i =
        injectFT[Task, DriverEff].compose(AtomicRef.fromTaskRef(stateRef)) :+:
        injectFT[ConsoleIO, DriverEff]                                :+:
        injectFT[ReplFail, DriverEff]                                 :+:
        injectFT[Task, DriverEff].compose(Timing.toTask)              :+:
        injectFT[Task, DriverEff]                                     :+:
        fs
    } yield {
      val timingPrint = (id: ExecutionId, timings: ExecutionTimings) => for {
        state <- Free.liftF(Inject[Task, ReplEff[S, ?]].inj(stateRef.read))
        _ <- state.timingFormat match {
          case TimingFormat.Nothing | TimingFormat.OnlyTotal =>
            ().point[Free[ReplEff[S, ?], ?]]
          case TimingFormat.Tree =>
            val timingTree =
              ExecutionTimings.render(ExecutionTimings.toLabelledIntervalTree(id, timings)).shows
            Free.liftF(Inject[ConsoleIO, ReplEff[S, ?]].inj(ConsoleIO.PrintLn(timingTree)))
          case TimingFormat.Json =>
            val renderedJson =
              ExecutionTimings.asJson(id, ExecutionTimings.toLabelledIntervalTree(id, timings)).nospaces
            Free.liftF(Inject[ConsoleIO, ReplEff[S, ?]].inj(ConsoleIO.PrintLn(renderedJson)))
        }
      } yield ()
      implicit val SE = ScopeExecution.forFreeTask[ReplEff[S, ?], Nothing](timingRepository, timingPrint)
      (cmd => Repl.command[ReplEff[S, ?], Nothing](cmd, executionIdRef).foldMap(i))
    }
  }

  private val DF = Failure.Ops[String, DriverEff]

  private val mt: MainTask ~> DriverEffM =
    new (MainTask ~> DriverEffM) {
      def apply[A](mt: MainTask[A]): DriverEffM[A] =
        free.lift(mt.run).into[DriverEff].flatMap(_.fold(
          err => DF.fail(err),
          _.point[DriverEffM]))
    }

  def startRepl(quasarInter: CoreEff ~> QErrs_CRW_TaskM): Task[Unit] =
    for {
      qetmt   <- QErrs_CRW_Task.toMainTask
      runCmd  <- repl[CoreEff](mt compose qetmt compose quasarInter)
      _       <- driver(runCmd)
    } yield ()

  def safeMain(args: Vector[String]): Task[Unit] =
    logErrors(for {
      opts    <- CliOptions.parser.safeParse(args, CliOptions.default)

      cfgPath <- opts.config.fold(none[FsFile].point[MainTask])(cfg =>
        FsPath.parseSystemFile(cfg)
          .toRight(s"Invalid path to config file: $cfg.")
          .map(some))

      backends <- BackendConfig.fromBackends(IList.fromList(opts.backends)).liftM[MainErrT]

      _ <- initMetaStoreOrStart[CoreConfig](
        CmdLineConfig(cfgPath, backends, opts.cmd),
        (_, quasarInter) => startRepl(quasarInter).liftM[MainErrT],
        // The REPL does not allow you to change metastore
        // so no need to supply a function to persist the metastore
        _ => ().point[MainTask])
    } yield ())

  def main(args: Array[String]): Unit =
    safeMain(args.toVector).unsafePerformSync
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy