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

dotty.tools.scaladoc.renderers.Renderer.scala Maven / Gradle / Ivy

There is a newer version: 3.6.0-RC1-bin-20240903-21a3d39-NIGHTLY
Show newest version
package dotty.tools.scaladoc
package renderers

import util.HTML._
import collection.mutable.ListBuffer
import dotty.tools.scaladoc.site._
import java.nio.file.Paths
import java.nio.file.Path
import java.nio.file.Files

case class Page(link: Link, content: Member | ResolvedTemplate | String, children: Seq[Page], hidden: Boolean = false):
  def withNewChildren(newChildren: Seq[Page]) = copy(children = children ++ newChildren)

  def withTitle(newTitle: String) = copy(link = link.copy(name = newTitle))

  def hasFrame = content match
    case t: ResolvedTemplate => t.hasFrame
    case _ => true

abstract class Renderer(rootPackage: Member, val members: Map[DRI, Member], protected val extension: String = "html")(using ctx: DocContext)
  extends SiteRenderer, Resources, Locations, Writer:
  protected val args = summon[DocContext].args
  val staticSite = summon[DocContext].staticSiteContext

  val effectiveMembers = members

  protected def memberPage(member: Member): Page =
    val childrenPages = member.members.filter(_.needsOwnPage)
    Page(Link(member.name, member.dri), member, childrenPages.map(memberPage))

  val rootApiPage: Option[Page] = Some(memberPage(rootPackage)).filter(_.children.nonEmpty).map(_.withTitle(ctx.args.name))

  val rootDocsPage: Option[Page] = staticSite match
      case None => None
      case Some(siteContext) =>
        val rootTemplate = siteContext.staticSiteRoot.rootTemplate

        // Below code is for walking in order the tree and modifing its nodes basing on its neighbours

        // We add dummy guards
        val notHidden: Seq[Option[LoadedTemplate]] = None +: siteContext.allTemplates.filterNot(_.hidden).map(Some(_)) :+ None

        // Let's gather the list of maps for each template with its in-order neighbours
        val newSettings: List[Map[String, Object]] = notHidden.sliding(size = 3, step = 1).map {
          case None :: None :: Nil =>
            Map.empty
          case prev :: mid :: next :: Nil =>
            def link(sibling: Option[LoadedTemplate]): Option[String] =
              def realPath(path: Path) = if Files.isDirectory(path) then Paths.get(path.toString, "index.html") else path
              sibling.map { n =>
                val realMidPath = realPath(mid.get.file.toPath)
                val realSiblingPath = realPath(n.file.toPath)
                realMidPath.relativize(realSiblingPath).toString.stripPrefix("../")
              }
            List(
              for {
                link <- link(prev)
                p <- prev
              } yield (
                "previous" -> Map(
                  "title" -> p.templateFile.title.name,
                  "url" -> link
                )
              ),
              for {
                link <- link(next)
                n <- next
              } yield (
                "next" -> Map(
                  "title" -> n.templateFile.title.name,
                  "url" -> link
                )
              ),
            ).flatten.toMap
        }.toList

        def updateSettings(templates: Seq[LoadedTemplate], additionalSettings: ListBuffer[Map[String, Object]]): List[LoadedTemplate] =
          val updatedTemplates = List.newBuilder[LoadedTemplate]
          for template <- templates do
            val head: Map[String, Object] =
              if template.hidden then Map.empty
              else additionalSettings.remove(0)
            val current: Map[String, Object] = template.templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]]
            val updatedTemplateFile = template.templateFile.copy(settings = template.templateFile.settings.updated("page", head ++ current))
            updatedTemplates += template.copy(
              templateFile = updatedTemplateFile,
              children = updateSettings(template.children, additionalSettings)
            )
          updatedTemplates.result()

        val newTemplates = updateSettings(Seq(rootTemplate), newSettings.to(ListBuffer))
        val templatePages = newTemplates.map(templateToPage(_, siteContext))

        val newRoot = newTemplates.head

        Some(newRoot).filter(r => r.children.nonEmpty || r.templateFile.rawCode.nonEmpty)
          .map(templateToPage(_, siteContext))

  val redirectPages: Seq[Page] = staticSite.fold(Seq.empty)(siteContext => siteContext.redirectTemplates.map {
    case (template, driFrom, driTo) =>
      val redirectTo = pathToPage(driFrom, driTo)
      templateToPage(template.copy(templateFile = template.templateFile.copy(settings = template.templateFile.settings ++ Map("redirectTo" -> redirectTo))), siteContext)
  })

  /**
   * Here we have to retrive index pages from hidden pages and replace fake index pages in navigable page tree.
   */
  val allPages: Seq[Page] =
    val all = rootApiPage ++ rootDocsPage ++ redirectPages
    // We need to check for conflicts only if we have top-level member called docs
    val hasPotentialConflict =
      rootPackage.members.exists(m => m.name.startsWith("docs"))

    if hasPotentialConflict then
      def walk(page: Page): Unit =
        if page.link.dri.isStaticFile then
          val dest = absolutePath(page.link.dri)
          if apiPaths.contains(dest) then
            report.error(s"Conflict between static page and API member for $dest. $pathsConflictResoultionMsg")
          page.children.foreach(walk)

      all.foreach(walk)

    all.toSeq

  def renderContent(page: Page): PageContent = page.content match
    case m: Member =>
      val signatureRenderer = new SignatureRenderer:
        def currentDri: DRI = page.link.dri
        def link(dri: DRI): Option[String] =
          dri.externalLink.orElse(
            Some(pathToPage(currentDri, dri)).filter(_ != UnresolvedLocationLink)
          )

      MemberRenderer(signatureRenderer).fullMember(m)
    case t: ResolvedTemplate => siteContent(page.link.dri, t)
    case a: String =>  PageContent(raw(a), Seq.empty)



  protected def canonicalUrl(l: String): AppliedTag | String =
    val canon = args.docCanonicalBaseUrl
    if !canon.isEmpty then
      val canonicalUrl = if canon.endsWith("/") then canon else canon + "/"
      link(rel := "canonical", href := canonicalUrl + l)
    else
      "" // return empty tag

  /**
   * Main method rendering all the pages
   */
  def render(): Unit =
    val sites = allPages.map(renderPage(_, Vector.empty))

  /**
   * Handler to prepare the content to be rendered. It's a good place to organize frame, footers, front-matter, etc.
   */
  def pageContent(page: Page, parents: Vector[Link]): AppliedTag

  /**
   * Method to be overriden by concrete renderer to render single page
   */
  def renderPage(page: Page, parents: Vector[Link]): Seq[String] =
    val newParents = parents :+ page.link
    val content = pageContent(page, newParents)
    write(page.link.dri, content, extension) +: page.children.flatMap(renderPage(_, newParents))






© 2015 - 2024 Weber Informatics LLC | Privacy Policy