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

org.scalajs.cli.Scalajsp.scala Maven / Gradle / Ivy

/*                     __                                                              *\
**     ________ ___   / /  ___      __ ____  Scala.js CLI                              **
**    / __/ __// _ | / /  / _ | __ / // __/  (c) 2013-2014, LAMP/EPFL                  **
**  __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \    (c) 2017-2022 Scala.js Sébastien Doeraene **
** /____/\___/_/ |_/____/_/ | |__/ /____/    http://scala-js.org/                      **
**                          |/____/                                                    **
\*                                                                                     */

package org.scalajs.cli

import org.scalajs.ir.ScalaJSVersions
import org.scalajs.ir.Trees.{Tree, ClassDef}
import org.scalajs.ir.Printers.IRTreePrinter

import org.scalajs.linker._
import org.scalajs.linker.interface._
import org.scalajs.linker.interface.unstable.IRFileImpl
import org.scalajs.linker.standard._

import scala.collection.immutable

import scala.concurrent._
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global

import scala.util.{Failure, Success}

import java.io.{Console => _, _}
import java.util.zip.{ZipFile, ZipEntry}
import java.nio.file.Path

object Scalajsp {

  private case class Options(
      jar: Option[File] = None,
      fileNames: immutable.Seq[String] = Nil
  )

  def main(args: Array[String]): Unit = {
    val parser = new scopt.OptionParser[Options]("scalajsp") {
      head("scalajsp", ScalaJSVersions.current)
      arg[String](" ...")
        .unbounded()
        .action { (x, c) => c.copy(fileNames = c.fileNames :+ x) }
        .text("*.sjsir file to display content of")
      opt[File]('j', "jar")
        .valueName("")
        .action { (x, c) => c.copy(jar = Some(x)) }
        .text("Read *.sjsir file(s) from the given JAR.")
      opt[Unit]('s', "supported")
        .action { (_, _) =>
          printSupported(); exit(0)
        }
        .text("Show supported Scala.js IR versions")
      version("version")
        .abbr("v")
        .text("Show scalajsp version")
      help("help")
        .abbr("h")
        .text("prints this usage text")

      override def showUsageOnError = Some(true)
    }

    for {
      options <- parser.parse(args, Options())
      fileName <- options.fileNames
    } {
      val vfile = options.jar
        .map { jar =>
          readFromJar(jar, fileName)
        }
        .getOrElse {
          readFromFile(fileName)
        }

      displayFileContent(Await.result(vfile, Duration.Inf), options)
    }
  }

  private def printSupported(): Unit = {
    import ScalaJSVersions._
    println(s"Scala.js IR library version is: $current")
    println(s"Supports Scala.js IR versions up to $binaryEmitted")
  }

  private def displayFileContent(vfile: IRFile, opts: Options): Unit = {
    val tree = Await.result(IRFileImpl.fromIRFile(vfile).tree, Duration.Inf)
    new IRTreePrinter(stdout).print(tree)
    stdout.write('\n')
    stdout.flush()
  }

  private def fail(msg: String): Nothing = {
    Console.err.println(msg)
    exit(1)
  }

  private def exit(code: Int): Nothing = {
    System.exit(code)
    throw new AssertionError("unreachable")
  }

  private def readFromFile(fileName: String): Future[IRFile] = {
    val file = new File(fileName)

    if (!file.exists) {
      fail(s"No such file: $fileName")
    } else if (!file.canRead) {
      fail(s"Unable to read file: $fileName")
    } else {
      PathIRFile(file.toPath())
    }
  }

  private def readFromJar(jar: File, name: String): Future[IRFile] = {
    /* This could be more efficient if we only read the relevant entry. But it
     * probably does not matter, and this implementation is very simple.
     */

    def findRequestedClass(sjsirFiles: Seq[IRFile]): Future[IRFile] = {
      Future
        .traverse(sjsirFiles) { irFile =>
          val ir = IRFileImpl.fromIRFile(irFile)
          ir.entryPointsInfo
            .map { i =>
              if (i.className.nameString == name) Success(Some(ir))
              else Success(None)
            }
            .recover { case t => Failure(t) }
        }
        .map { irs =>
          irs
            .collectFirst { case Success(Some(f)) =>
              f
            }
            .getOrElse {
              fail(s"No such class in jar: $name")
            }
        }
    }

    val cache = StandardImpl.irFileCache().newCache

    for {
      (containers, _) <- PathIRContainer.fromClasspath(jar.toPath() :: Nil)
      irFiles <- cache.cached(containers)
      requestedFile <- findRequestedClass(irFiles)
    } yield {
      requestedFile
    }
  }

  private val stdout =
    new BufferedWriter(new OutputStreamWriter(Console.out, "UTF-8"))

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy