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

scala.build.internal.markdown.MarkdownCodeBlock.scala Maven / Gradle / Ivy

package scala.build.internal.markdown

import scala.annotation.tailrec
import scala.build.Position
import scala.build.errors.{BuildException, MarkdownUnclosedBackticksError}
import scala.collection.mutable
import scala.jdk.CollectionConverters.*

/** Representation for a (closed) code block contained in Markdown
  *
  * @param info
  *   a list of tags tied to a given code block
  * @param body
  *   the code block content
  * @param startLine
  *   starting line on which the code block was defined (excluding backticks)
  * @param endLine
  *   end line on which the code block was closed (excluding backticks)
  */
case class MarkdownCodeBlock(
  info: Seq[String],
  body: String,
  startLine: Int,
  endLine: Int
) {

  /** @return
    *   `true` if this snippet should be ignored, `false` otherwise
    */
  def shouldIgnore: Boolean = info.head != "scala" || info.contains("ignore")

  /** @return
    *   `true` if this snippet should have its scope reset, `false` otherwise
    */
  def resetScope: Boolean = info.contains("reset")

  /** @return
    *   `true` if this snippet is a test snippet, `false` otherwise
    */
  def isTest: Boolean = info.contains("test")

  /** @return
    *   `true` if this snippet is a raw snippet, `false` otherwise
    */
  def isRaw: Boolean = info.contains("raw")
}

object MarkdownCodeBlock {

  /** Finds all code snippets in given input
    *
    * @param subPath
    *   the project [[os.SubPath]] to the Markdown file
    * @param md
    *   Markdown file in a `String` format
    * @param maybeRecoverOnError
    *   function potentially recovering on errors
    * @return
    *   list of all found snippets
    */
  def findCodeBlocks(
    subPath: os.SubPath,
    md: String,
    maybeRecoverOnError: BuildException => Option[BuildException] = Some(_)
  ): Either[BuildException, Seq[MarkdownCodeBlock]] = {
    val allLines = md
      .lines()
      .toList
      .asScala
    @tailrec
    def findCodeBlocksRec(
      lines: Seq[String],
      closedFences: Seq[MarkdownCodeBlock] = Seq.empty,
      maybeOpenFence: Option[MarkdownOpenFence] = None,
      currentIndex: Int = 0
    ): Either[BuildException, Seq[MarkdownCodeBlock]] = lines -> maybeOpenFence match {
      case (Seq(currentLine, tail*), mof) =>
        val (newClosedFences, newOpenFence) = mof match {
          case None => closedFences -> MarkdownOpenFence.maybeFence(currentLine, currentIndex)
          case Some(openFence) =>
            val backticksStart = currentLine.indexOf(openFence.backticks)
            if backticksStart == openFence.indent &&
              currentLine.forall(c => c == '`' || c.isWhitespace)
            then (closedFences :+ openFence.closeFence(currentIndex, allLines.toArray)) -> None
            else closedFences -> Some(openFence)
        }
        findCodeBlocksRec(tail, newClosedFences, newOpenFence, currentIndex + 1)
      case (Nil, Some(openFence)) =>
        maybeRecoverOnError(openFence.toUnclosedBackticksError(os.pwd / subPath))
          .map(e => Left(e))
          .getOrElse(Right(closedFences))
      case _ => Right(closedFences)
    }
    findCodeBlocksRec(allLines.toSeq).map(_.filter(!_.shouldIgnore))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy