diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index 3c35c761caf2..d71fe03a9c13 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -44,7 +44,13 @@ class HtmlRenderer(rootPackage: Member, members: Map[DRI, Member])(using ctx: Do Using(Files.walk(file)) { stream => stream.iterator().asScala.toSeq .map(from => Resource.File(resourceFile.toPath.relativize(from).toString, from)) - }.get + }.fold ( + { t => + report.warn(s"Error occured while processing _assets file.", t) + Seq.empty + }, + identity + ) } } val resources = staticSiteResources ++ allResources(allPages) ++ onlyRenderedResources @@ -115,7 +121,7 @@ class HtmlRenderer(rootPackage: Member, members: Map[DRI, Member])(using ctx: Do ) ) - nav.children match + nav.children.filterNot(_.hidden) match case Nil => isSelected -> div(cls := s"ni ${if isSelected then "expanded" else ""}")(linkHtml()) case children => val nested = children.map(renderNested(_)) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala index 58627ecc7964..1c55cb8124e1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala @@ -34,7 +34,6 @@ trait Locations(using ctx: DocContext): cache.get(dri) match case null => val path = dri match - // case `docsRootDRI` => List("docs", "index") case `apiPageDRI` => if ctx.args.apiSubdirectory && ctx.staticSiteContext.nonEmpty then List("api", "index") diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala index ef83b4f92196..45a080d4828a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala @@ -15,7 +15,7 @@ import java.nio.file.Files import java.nio.file.FileVisitOption import java.io.File -case class Page(link: Link, content: Member | ResolvedTemplate | String, children: Seq[Page]): +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)) @@ -74,12 +74,12 @@ abstract class Renderer(rootPackage: Member, val members: Map[DRI, Member], prot ) updatedTemplates.result() - val newTemplates = updateSettings(Seq(siteContext.staticSiteRoot.rootTemplate), newSettings.to(ListBuffer)) + val newTemplates = updateSettings(Seq(rootTemplate), newSettings.to(ListBuffer)) val templatePages = newTemplates.map(templateToPage(_, siteContext)) val newRoot = newTemplates.head - if newRoot.children.size == 0 && newRoot.templateFile.rawCode == "" + if newRoot.children.isEmpty && newRoot.templateFile.rawCode.isEmpty then rootPckPage.withTitle(args.name) else { val newRootPage = templateToPage(newRoot, siteContext) diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala index 123cdb6316fd..ad97b19fbb24 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/SiteRenderer.scala @@ -22,7 +22,7 @@ trait SiteRenderer(using DocContext) extends Locations: def templateToPage(t: LoadedTemplate, staticSiteCtx: StaticSiteContext): Page = val dri = staticSiteCtx.driFor(t.file.toPath) val content = ResolvedTemplate(t, staticSiteCtx) - Page(Link(t.templateFile.title.name, dri), content, t.children.map(templateToPage(_, staticSiteCtx))) + Page(Link(t.templateFile.title.name, dri), content, t.children.map(templateToPage(_, staticSiteCtx)), t.hidden) private val HashRegex = "([^#]+)(#.+)".r diff --git a/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala b/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala index 84b84b03850a..c8388a8112bd 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/LoadedTemplate.scala @@ -16,7 +16,8 @@ case class LazyEntry(getKey: String, value: () => String) extends JMapEntry[Stri case class LoadedTemplate( templateFile: TemplateFile, children: List[LoadedTemplate], - file: File): + file: File, + hidden: Boolean = false): private def brief(ctx: StaticSiteContext): String = try diff --git a/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala b/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala index 2b3ddd552207..f383156a2b3d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/SidebarParser.scala @@ -6,48 +6,86 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.`type`.TypeReference; import collection.JavaConverters._ import java.util.Optional +import scala.beans._ enum Sidebar: - case Root(index: Option[String], pages: List[Sidebar]) - case Category(title: Option[String], indexPath: Option[String], nested: List[Sidebar], directory: Option[String]) - case Page(title: Option[String], pagePath: String) + case Root(index: Option[String], pages: List[Sidebar.Child]) + case Category( + title: Option[String], + indexPath: Option[String], + nested: List[Sidebar.Child], + directory: Option[String] + ) + case Page(title: Option[String], pagePath: String, hidden: Boolean) object Sidebar: + + type Child = Category | Page case class RawRoot(var rootIndex: String, var pages: JList[RawInput]): def this() = this("", JList()) def setRootIndex(s: String) = rootIndex = s def setPages(l: JList[RawInput]) = pages = l - case class RawInput(var title: String, var page: String, var index: String, var subsection: JList[RawInput], var directory: String): - def this() = this("", "", "", JList(), "") - - def setTitle(t: String) = this.title = t - def setPage(p: String) = this.page = p - def setIndex(i: String) = this.index = i - def setSubsection(s: JList[RawInput]) = this.subsection = s - def setDirectory(d: String) = this.directory = d + case class RawInput( + @BeanProperty var title: String, + @BeanProperty var page: String, + @BeanProperty var index: String, + @BeanProperty var subsection: JList[RawInput], + @BeanProperty var directory: String, + @BooleanBeanProperty var hidden: Boolean + ): + def this() = this("", "", "", JList(), "", false) private object RootTypeRef extends TypeReference[RawRoot] - private def toSidebar(r: RawInput): Sidebar = r match - case RawInput(title, page, index, subsection, dir) if page.nonEmpty && index.isEmpty && subsection.isEmpty() || title == "Blog" => - Sidebar.Page(Option.when(title.nonEmpty)(title), page) - case RawInput(title, page, index, subsection, dir) if page.isEmpty && (!subsection.isEmpty() || !index.isEmpty()) => + private def toSidebar(r: RawInput)(using CompilerContext): Sidebar.Child = r match + case RawInput(title, page, index, subsection, dir, hidden) if page.nonEmpty && index.isEmpty && subsection.isEmpty() => + Sidebar.Page(Option.when(title.nonEmpty)(title), page, hidden) + case RawInput(title, page, index, subsection, dir, hidden) if page.isEmpty && (!subsection.isEmpty() || !index.isEmpty()) => Sidebar.Category(Option.when(title.nonEmpty)(title), Option.when(index.nonEmpty)(index), subsection.asScala.map(toSidebar).toList, Option.when(dir.nonEmpty)(dir)) + case RawInput(title, page, index, subsection, dir, hidden) => + report.error(s"Error parsing YAML configuration file.\n$schemaMessage") + Sidebar.Page(None, page, hidden) - def load(content: String): Sidebar.Root = - val mapper = ObjectMapper(YAMLFactory()) - val root: RawRoot = mapper.readValue(content, RootTypeRef) + private def schemaMessage: String = + s"""Static site YAML configuration file should comply to the following description: + |rootIndex: # optional + |pages: + | - | + | + |: + | title: # optional + | index: # optional + | directory: # optional + | subsection: # optional + | - | + | # either index or subsection needs to be present + |: + | title: # optional + | page: + | hidden: # optional + | + |For more information visit: + |https://docs.scala-lang.org/scala3/guides/scaladoc/static-site.html + |""".stripMargin - val rootIndex: String = root.rootIndex - val pages: List[Sidebar] = root.pages.asScala.toList.map(toSidebar) - Sidebar.Root(Option.when(rootIndex.nonEmpty)(rootIndex), pages) - - def load(file: java.io.File): Sidebar.Root = + def load(content: String | java.io.File)(using CompilerContext): Sidebar.Root = + import scala.util.Try val mapper = ObjectMapper(YAMLFactory()) - val root: RawRoot = mapper.readValue(file, RootTypeRef) + def readValue = content match + case s: String => mapper.readValue(s, RootTypeRef) + case f: java.io.File => mapper.readValue(f, RootTypeRef) + + val root: RawRoot = Try(readValue) + .fold( + { e => + report.warn(schemaMessage, e) + RawRoot("", java.util.Collections.emptyList()) + }, + identity + ) val rootIndex: String = root.rootIndex - val pages: List[Sidebar] = root.pages.asScala.toList.map(toSidebar) + val pages: List[Sidebar.Child] = root.pages.asScala.toList.map(toSidebar) Sidebar.Root(Option.when(rootIndex.nonEmpty)(rootIndex), pages) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteLoader.scala b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteLoader.scala index 7fd2410de140..9ec6bf032ce4 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteLoader.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteLoader.scala @@ -24,10 +24,22 @@ class StaticSiteLoader(val root: File, val args: Scaladoc.Args)(using StaticSite } } + /** Method loading static site structure based on YAML configuration file. + * + * The rendered static site will only contain pages that are present in YAML. + * The following rules are applied: + * - Each subsection will be a separate directory. + * - Nested subsections will result in nested directories. + * - If the subsection object contains location of index and doesn't contain any item, + * items are loaded using file system from the directory of the index file. + * - By default, directory name is a subsection title converted to kebab case. + * However, you can override default name by setting "directory" property of the subsection object. + * + */ def loadBasedOnYaml(yamlRoot: Sidebar.Root): StaticSiteRoot = { val rootDest = ctx.docsPath.resolve("index.html").toFile val rootIndex = yamlRoot.index - .map(Paths.get(root.getPath, _).toFile) + .map(ctx.docsPath.resolve(_).toFile) .filter(_.exists) .fold(emptyTemplate(rootDest, "index")) { f => val loaded = loadTemplateFile(f) @@ -36,13 +48,13 @@ class StaticSiteLoader(val root: File, val args: Scaladoc.Args)(using StaticSite loaded }.copy(title = TemplateName.FilenameDefined(args.name)) - def loadChild(pathFromRoot: Path): Sidebar => LoadedTemplate = { + def loadChild(pathFromRoot: Path): Sidebar.Child => LoadedTemplate = { case Sidebar.Category(optionTitle, optionIndexPath, nested, dir) => val indexPageOpt = optionIndexPath .map(relativizeIfNeeded) .map(_.toFile) .filter(_.exists) - .map(loadTemplateFile) + .map(loadTemplateFile(_)) val title = ( optionTitle.map(TemplateName.SidebarDefined(_)) ++ indexPageOpt.map(_.title) @@ -70,18 +82,12 @@ class StaticSiteLoader(val root: File, val args: Scaladoc.Args)(using StaticSite } LoadedTemplate(indexPage, children, categoryPath.resolve("index.html").toFile) - case Sidebar.Page(optionTitle, pagePath) => + case Sidebar.Page(optionTitle, pagePath, hidden) => val path = relativizeIfNeeded(pagePath) val file = path.toFile - val templateFile = loadTemplateFile(file) - val withUpdatedTitle = optionTitle.fold(templateFile) { t => templateFile.title match - case _: TemplateName.FilenameDefined => templateFile.copy(title = TemplateName.SidebarDefined(t)) - case _ => templateFile - } - LoadedTemplate(withUpdatedTitle, List.empty, pathFromRoot.resolve(file.getName).toFile) - case Sidebar.Root(_, _) => - // Cannot happen - ??? + val title = optionTitle.map(TemplateName.SidebarDefined(_)) + val templateFile = loadTemplateFile(file, title) + LoadedTemplate(templateFile, List.empty, pathFromRoot.resolve(file.getName).toFile, hidden) } val rootTemplate = LoadedTemplate(rootIndex, yamlRoot.pages.map(c => loadChild(ctx.docsPath)(c)) ++ loadBlog(), rootDest) val mappings = createMapping(rootTemplate) @@ -175,7 +181,7 @@ class StaticSiteLoader(val root: File, val args: Scaladoc.Args)(using StaticSite val children = currRoot.listFiles.toList .filter(_.toPath != indexPageOpt.getOrElse(null)) - Some(LoadedTemplate(indexPage, children.flatMap(loadRecursively(_, destMappingFunc)), destMappingFunc(indexPage.file))) + Some(LoadedTemplate(indexPage, children.flatMap(loadRecursively(_, destMappingFunc)).sortBy(_.templateFile.title.name), destMappingFunc(indexPage.file))) } else if (currRoot.exists && ctx.siteExtensions.exists(ext => currRoot.getName.endsWith(ext))) { val templateFile = loadTemplateFile(currRoot) diff --git a/scaladoc/src/dotty/tools/scaladoc/site/common.scala b/scaladoc/src/dotty/tools/scaladoc/site/common.scala index 6c7677729e89..e409fe4ae636 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/common.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/common.scala @@ -58,7 +58,7 @@ final val LineSeparator = "\n" def yamlParser(using ctx: StaticSiteContext): Parser = Parser.builder(defaultMarkdownOptions).build() -def loadTemplateFile(file: File)(using ctx: StaticSiteContext): TemplateFile = { +def loadTemplateFile(file: File, defaultTitle: Option[TemplateName] = None)(using ctx: StaticSiteContext): TemplateFile = { val lines = Files.readAllLines(file.toPath).asScala.toList val (config, content) = if (lines.head == ConfigSeparator) { @@ -105,7 +105,7 @@ def loadTemplateFile(file: File)(using ctx: StaticSiteContext): TemplateFile = { rawCode = content.mkString(LineSeparator), settings = settings, name = name, - title = stringSetting(allSettings, "title").map(TemplateName.YamlDefined(_)).getOrElse(TemplateName.FilenameDefined(name)), + title = stringSetting(allSettings, "title").map(TemplateName.YamlDefined(_)).orElse(defaultTitle).getOrElse(TemplateName.FilenameDefined(name)), hasFrame = !stringSetting(allSettings, "hasFrame").contains("false"), resources = (listSetting(allSettings, "extraCSS") ++ listSetting(allSettings, "extraJS")).flatten.toList, layout = stringSetting(allSettings, "layout"), diff --git a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala index ace8dd97a2e0..8d7cc04cfec6 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/templates.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/templates.scala @@ -115,25 +115,9 @@ case class TemplateFile( // Library requires mutable maps.. val mutableProperties = new JHashMap(ctx.properties.transform((_, v) => asJavaElement(v)).asJava) - val tag = new Tag("highlight"): - override def render(context: TemplateContext, nodes: Array[? <: LNode]): Object = - super.asString(nodes(0).render(context), context) match - case "diff" => - s"
${super.asString(nodes(1).render(context), context)}
\n\n" - case _ => - report.warn("Unsupported highlight value. Currenlty supported values are: `diff`", file)(using ssctx.outerCtx) - s"```${super.asString(nodes(1).render(context), context)}```\n\n" - - val tag2 = new Tag("link"): - override def render(context: TemplateContext, nodes: Array[? <: LNode]): Object = - super.asString(nodes(0).render(context), context) match - case sth => - report.warn(s"Unsupported link tag. Link to $sth can't be resolved", file)(using ssctx.outerCtx) - "/" - val parseSettings = ParseSettings.Builder().withFlavor(Flavor.JEKYLL).build() - val rendered = Template.parse(this.rawCode, parseSettings).`with`(tag).`with`(tag2).render(mutableProperties) + val rendered = Template.parse(this.rawCode, parseSettings).render(mutableProperties) // We want to render markdown only if next template is html val code = if (isHtml || layoutTemplate.exists(!_.isHtml)) rendered else diff --git a/scaladoc/test/dotty/tools/scaladoc/site/SidebarParserTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/SidebarParserTest.scala index b40a7c9c5e52..2f9584c88b96 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/SidebarParserTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/SidebarParserTest.scala @@ -8,7 +8,6 @@ import org.junit.Assert._ class SidebarParserTest: private val sidebar = """pages: - - title: Blog - title: My title page: my-page1.md - page: my-page2.md @@ -16,6 +15,7 @@ class SidebarParserTest: - title: Reference subsection: - page: my-page3.md + hidden: true - index: my-page4/index.md subsection: - page: my-page4/my-page4.md @@ -37,16 +37,15 @@ class SidebarParserTest: Sidebar.Root( None, List( - Sidebar.Page(Some("Blog"), ""), - Sidebar.Page(Some("My title"), "my-page1.md"), - Sidebar.Page(None, "my-page2.md"), - Sidebar.Page(None, "my-page3/subsection"), - Sidebar.Category(Some("Reference"), None, List(Sidebar.Page(None, "my-page3.md")), None), - Sidebar.Category(None, Some("my-page4/index.md"), List(Sidebar.Page(None, "my-page4/my-page4.md")), None), - Sidebar.Category(Some("My subsection"), Some("my-page5/index.md"), List(Sidebar.Page(None, "my-page5/my-page5.md")), None), - Sidebar.Category(None, None, List(Sidebar.Page(None, "my-page7/my-page7.md")), None), - Sidebar.Category(None, Some("my-page6/index.md"), List(Sidebar.Category(None, Some("my-page6/my-page6/index.md"), List(Sidebar.Page(None, "my-page6/my-page6/my-page6.md")), None)), None), + Sidebar.Page(Some("My title"), "my-page1.md", false), + Sidebar.Page(None, "my-page2.md", false), + Sidebar.Page(None, "my-page3/subsection", false), + Sidebar.Category(Some("Reference"), None, List(Sidebar.Page(None, "my-page3.md", true)), None), + Sidebar.Category(None, Some("my-page4/index.md"), List(Sidebar.Page(None, "my-page4/my-page4.md", false)), None), + Sidebar.Category(Some("My subsection"), Some("my-page5/index.md"), List(Sidebar.Page(None, "my-page5/my-page5.md", false)), None), + Sidebar.Category(None, None, List(Sidebar.Page(None, "my-page7/my-page7.md", false)), None), + Sidebar.Category(None, Some("my-page6/index.md"), List(Sidebar.Category(None, Some("my-page6/my-page6/index.md"), List(Sidebar.Page(None, "my-page6/my-page6/my-page6.md", false)), None)), None), ) ), - Sidebar.load(sidebar) + Sidebar.load(sidebar)(using testContext) )