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

laika.internal.rewrite.TemplateRewriter.scala Maven / Gradle / Ivy

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * 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 laika.internal.rewrite

import cats.implicits.*
import laika.api.config.ConfigError
import laika.ast.RewriteRules.RewriteRulesBuilder
import laika.ast.*
import laika.config.LaikaKeys
import laika.api.config.ConfigError.ValidationFailed
import laika.internal.rewrite.ReferenceResolver.CursorKeys
import laika.parse.{ LineSource, SourceCursor }

import scala.annotation.tailrec

private[laika] trait TemplateRewriter {

  private val defaultTemplateRoot: TemplateRoot = {
    val src = s"$${${CursorKeys.documentContent}}"
    TemplateRoot(
      TemplateContextReference(
        CursorKeys.documentContent,
        required = true,
        LineSource(src, SourceCursor(src))
      )
    )
  }

  private def defaultTemplate(format: String): TemplateDocument =
    TemplateDocument(DefaultTemplatePath.forSuffix(format), defaultTemplateRoot)

  private def shouldRender(formatSelector: String)(content: Cursor): Boolean =
    content.target.targetFormats.contains(formatSelector)

  /** Selects and applies the templates for the specified output format to all documents
    * within the specified tree cursor recursively.
    */
  def applyTemplates(
      tree: DocumentTreeRoot,
      rules: RewriteRulesBuilder,
      context: OutputContext
  ): Either[ConfigError, DocumentTreeRoot] = {
    for {
      cursor   <- RootCursor(tree, Some(context))
      newCover <- cursor.coverDocument
        .filter(shouldRender(context.formatSelector))
        .traverse(applyTemplate(_, rules, context))
      newTree  <- applyTemplates(cursor.tree, rules, context)
    } yield {
      cursor.target.withCoverDocument(newCover).modifyTree(_ => newTree)
    }
  }

  private def applyTemplates(
      cursor: TreeCursor,
      rules: RewriteRulesBuilder,
      context: OutputContext
  ): Either[ConfigError, DocumentTree] = {
    for {
      newTitle   <- cursor.titleDocument.filter(shouldRender(context.formatSelector)).traverse(
        applyTemplate(_, rules, context)
      )
      newContent <- cursor.children.filter(shouldRender(context.formatSelector)).toList.traverse {
        case doc: DocumentCursor => applyTemplate(doc, rules, context)
        case tree: TreeCursor    => applyTemplates(tree, rules, context)
      }
    } yield {
      cursor.target.replaceContent(newContent).withTitleDocument(newTitle).withoutTemplates
    }
  }

  private def applyTemplate(
      cursor: DocumentCursor,
      rules: RewriteRulesBuilder,
      context: OutputContext
  ): Either[ConfigError, Document] = for {
    template <- selectTemplate(cursor, context.fileSuffix).map(
      _.getOrElse(defaultTemplate(context.fileSuffix))
    )
    doc      <- applyTemplate(cursor, rules, template)
  } yield doc

  /** Applies the specified template to the target of the specified document cursor.
    */
  def applyTemplate(
      cursor: DocumentCursor,
      rules: RewriteRulesBuilder,
      template: TemplateDocument
  ): Either[ConfigError, Document] = {
    cursor.applyTemplate(template).flatMap { mergedCursor =>
      rules(mergedCursor).map { docRules =>
        val newContent = docRules.rewriteBlock(template.content)
        val newRoot    = newContent match {
          case TemplateRoot(List(TemplateElement(root: RootElement, _, _)), _) => root
          case TemplateRoot(List(EmbeddedRoot(content, _, _)), _) => RootElement(content)
          case other                                              => RootElement(other)
        }
        mergedCursor.target.withContent(newRoot)
      }
    }
  }

  private[laika] def selectTemplate(
      cursor: DocumentCursor,
      format: String
  ): Either[ConfigError, Option[TemplateDocument]] = {
    val config       = cursor.config
    val templatePath = config.getOpt[Path](LaikaKeys.template).flatMap {
      case None       => config.getOpt[Path](LaikaKeys.template(format))
      case Some(path) => Right(Some(path))
    }

    templatePath.flatMap {
      case Some(path) =>
        cursor.root.target.tree
          .selectTemplate(path.relative)
          .map(Some(_))
          .toRight(ValidationFailed(s"Template with path '$path' not found"))

      case None =>
        val templatePath = DefaultTemplatePath.forSuffix(format).relative
        @tailrec def templateForTree(tree: TreeCursor): Option[TemplateDocument] =
          (tree.target.selectTemplate(templatePath), tree.parent) match {
            case (None, Some(parent)) => templateForTree(parent)
            case (Some(template), _)  => Some(template)
            case (None, None)         => None
          }

        Right(templateForTree(cursor.parent))
    }
  }

}

private[laika] object TemplateRewriter extends TemplateRewriter




© 2015 - 2024 Weber Informatics LLC | Privacy Policy