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

mdoc.internal.markdown.MarkdownBuilder.scala Maven / Gradle / Ivy

The newest version!
package mdoc.internal.markdown

import mdoc.Reporter
import mdoc.document.Document
import mdoc.document._
import scala.meta._
import scala.meta.inputs.Input
import scala.meta.inputs.Position
import scala.util.Try
import mdoc.internal.pos.PositionSyntax._
import mdoc.internal.document.DocumentBuilder
import mdoc.internal.document.MdocNonFatal
import mdoc.internal.CompatClassloader
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.atomic.AtomicReference

object MarkdownBuilder {

  def default(): MarkdownCompiler = fromClasspath(classpath = "", scalacOptions = "")

  def buildDocument(
      compiler: MarkdownCompiler,
      reporter: Reporter,
      sectionInputs: List[SectionInput],
      instrumented: Instrumented,
      filename: String
  ): EvaluatedDocument = {
    val instrumentedInput = InstrumentedInput(filename, instrumented.source)
    reporter.debug(s"$filename: instrumented code\n$instrumented")
    val compileInput = Input.VirtualFile(filename, instrumented.source)
    val edit = SectionInput.tokenEdit(sectionInputs, compileInput)
    val compiled = compiler.compile(
      compileInput,
      reporter,
      edit,
      "repl.MdocSession$",
      instrumented.fileImports
    )
    val doc = compiled match {
      case Some(cls) =>
        val ctor = cls.getDeclaredConstructor()
        ctor.setAccessible(true)
        val doc = ctor.newInstance().asInstanceOf[DocumentBuilder].$doc
        var evaluatedDoc = Document.empty(instrumentedInput)
        runInClassLoader(cls.getClassLoader()) { () =>
          try {
            evaluatedDoc = doc.build(instrumentedInput)
          } catch {
            case e: DocumentException =>
              val index = e.sections.length - 1
              val input = sectionInputs(index).input
              val pos =
                if (e.pos.isEmpty) {
                  Position.Range(input, 0, 0)
                } else {
                  val slice = Position.Range(
                    input,
                    e.pos.startLine,
                    e.pos.startColumn,
                    e.pos.endLine,
                    e.pos.endColumn
                  )
                  slice.toUnslicedPosition
                }
              reporter.error(pos, e.getCause)
              evaluatedDoc = Document(instrumentedInput, e.sections)
            case MdocNonFatal(e) =>
              reporter.error(e)
              evaluatedDoc = Document.empty(instrumentedInput)
          }
        }
        evaluatedDoc
      case None =>
        // An empty document will render as the original markdown
        Document.empty(instrumentedInput)
    }
    EvaluatedDocument(doc, sectionInputs)
  }

  def fromClasspath(classpath: String, scalacOptions: String): MarkdownCompiler = {
    val fullClasspath =
      if (classpath.isEmpty) defaultClasspath(_ => true)
      else {
        val base = Classpath(classpath)
        val runtime = defaultClasspath(path => {
          val pathString = path.toString
          pathString.contains("scala-library") ||
          pathString.contains("scala3-library") ||
          pathString.contains("scala-reflect") ||
          pathString.contains("fansi") ||
          pathString.contains("pprint") ||
          pathString.contains("mdoc-interfaces") ||
          (pathString.contains("mdoc") && pathString.contains("runtime")) ||
          (pathString.contains("mdoc") && pathString.contains("printing"))
        })
        base ++ runtime
      }
    new MarkdownCompiler(fullClasspath.syntax, scalacOptions)
  }

  private def runInClassLoader(classloader: ClassLoader)(f: () => Unit) = {
    val thread = new Thread {
      override def run: Unit = {
        f()
      }
    }
    thread.setContextClassLoader(classloader)
    thread.start()
    thread.join()
  }

  private def defaultClasspath(fn: Path => Boolean): Classpath = {
    val paths =
      CompatClassloader
        .getURLs(getClass.getClassLoader)
        .iterator
        .flatMap(url => Try(AbsolutePath(Paths.get(url.toURI))).toOption)
        .filter(p => fn(p.toNIO))
        .toList
    Classpath(paths)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy