diff --git a/docs/docs/index.md b/docs/docs/index.md index b83d4419e4c6..8fe7ce938186 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -1,6 +1,5 @@ --- layout: doc-page -title: "Dotty Documentation" --- Dotty is the project name for technologies that are considered for inclusion in Scala 3. Scala has diff --git a/project/Build.scala b/project/Build.scala index 25ecac0a125b..94071984599d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1159,6 +1159,7 @@ object Build { val testcasesOutputDir = taskKey[String]("Root directory where tests classses are generated") val testcasesSourceRoot = taskKey[String]("Root directory where tests sources are generated") + val testDocumentationRoot = taskKey[String]("Root directory where tests documentation are stored") val generateSelfDocumentation = taskKey[Unit]("Generate example documentation") // Note: the two tasks below should be one, but a bug in Tasty prevents that val generateScala3Documentation = taskKey[Unit]("Generate documentation for dotty lib") @@ -1532,7 +1533,9 @@ object Build { buildInfoKeys in Test := Seq[BuildInfoKey]( Build.testcasesOutputDir.in(Test), Build.testcasesSourceRoot.in(Test), + Build.testDocumentationRoot, ), + testDocumentationRoot := (baseDirectory.value / "test-documentations").getAbsolutePath, buildInfoPackage in Test := "dotty.dokka", BuildInfoPlugin.buildInfoScopedSettings(Test), BuildInfoPlugin.buildInfoDefaultSettings, diff --git a/scala3doc/src/dotty/dokka/Main.scala b/scala3doc/src/dotty/dokka/Main.scala index 7a568c73aa94..dfee60225ef7 100644 --- a/scala3doc/src/dotty/dokka/Main.scala +++ b/scala3doc/src/dotty/dokka/Main.scala @@ -78,13 +78,13 @@ case class Args( tastyRoots: Seq[File], classpath: String, output: File, - docsRoot: Option[String], - projectVersion: Option[String], - projectTitle: Option[String], - projectLogo: Option[String], - defaultSyntax: Option[Args.CommentSyntax], - sourceLinks: List[String], - revision: Option[String] + docsRoot: Option[String] = None, + projectVersion: Option[String] = None, + projectTitle: Option[String] = None, + projectLogo: Option[String] = None, + defaultSyntax: Option[Args.CommentSyntax] = None, + sourceLinks: List[String] = Nil, + revision: Option[String] = None ) object Args: @@ -121,12 +121,8 @@ enum DocConfiguration extends BaseDocConfiguration: * - [](package.DottyDokkaConfig) is our config for Dokka. */ object Main: - def main(args: Array[String]): Unit = + def main(parsedArgs: Args): Unit = try - val rawArgs = new RawArgs - new CmdLineParser(rawArgs).parseArgument(args:_*) - val parsedArgs = rawArgs.toArgs - val (files, dirs) = parsedArgs.tastyRoots.partition(_.isFile) val (providedTastyFiles, jars) = files.toList.map(_.getAbsolutePath).partition(_.endsWith(".tasty")) jars.foreach(j => if(!j.endsWith(".jar")) sys.error(s"Provided file $j is not jar not tasty file") ) @@ -147,11 +143,17 @@ object Main: new DokkaGenerator(new DottyDokkaConfig(config), DokkaConsoleLogger.INSTANCE).generate() println("Done") - - // Sometimes jvm is hanging, so we want to be sure that we force shout down the jvm - sys.exit(0) catch case a: Exception => a.printStackTrace() // Sometimes jvm is hanging, so we want to be sure that we force shout down the jvm sys.exit(1) + + def main(args: Array[String]): Unit = + val rawArgs = new RawArgs + new CmdLineParser(rawArgs).parseArgument(args:_*) + main(rawArgs.toArgs) + // Sometimes jvm is hanging, so we want to be sure that we force shout down the jvm + sys.exit(0) + + diff --git a/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala b/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala index c6c104a52373..163034ab75b6 100644 --- a/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala +++ b/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala @@ -41,7 +41,7 @@ case class LoadedTemplate(templateFile: TemplateFile, children: List[LoadedTempl def resolveToHtml(ctx: StaticSiteContext): ResolvedPage = val posts = children.map(_.lazyTemplateProperties(ctx)) - val site = templateFile.settings.getOrElse("site", Map.empty).asInstanceOf[Map[String, Object]] + def getMap(key: String) = templateFile.settings.getOrElse(key, Map.empty).asInstanceOf[Map[String, Object]] val sourceLinks = if !file.exists() then Nil else // TODO (https://github.com/lampepfl/scala3doc/issues/240): configure source root // toRealPath is used to turn symlinks into proper paths @@ -50,6 +50,7 @@ case class LoadedTemplate(templateFile: TemplateFile, children: List[LoadedTempl ctx.sourceLinks.pathTo(actualPath, operation = "edit").map("editSource" -> _ ) val updatedSettings = templateFile.settings ++ ctx.projectWideProperties + - ("site" -> (site + ("posts" -> posts))) + ("urls" -> sourceLinks.toMap) + ("site" -> (getMap("site") + ("posts" -> posts))) + ("urls" -> sourceLinks.toMap) + + ("page" -> (getMap("page") + ("title" -> templateFile.title))) templateFile.resolveInner(RenderingContext(updatedSettings, ctx.layouts)) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 66f6c4945bc8..aa22b6440a8e 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -20,7 +20,7 @@ import util.Try import scala.collection.JavaConverters._ -class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper], args: Args, val sourceLinks: SourceLinks): +class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper], val args: Args, val sourceLinks: SourceLinks): var memberLinkResolver: String => Option[DRI] = _ => None @@ -81,16 +81,15 @@ class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper], args: val topLevelFiles = if isBlog then Seq(from, new File(from, "_posts")) else Seq(from) val allFiles = topLevelFiles.filter(_.isDirectory).flatMap(_.listFiles()) val (indexes, children) = allFiles.flatMap(loadTemplate(_)).partition(_.templateFile.isIndexPage()) - if (indexes.size > 1) - // TODO (https://github.com/lampepfl/scala3doc/issues/238): provide proper error handling - println(s"ERROR: Multiple index pages for $from found in ${indexes.map(_.file)}") + def loadIndexPage(): TemplateFile = - val indexFiles = from.listFiles { file =>file.getName == "index.md" || file.getName == "index.html" } - indexFiles.size match - case 0 => emptyTemplate(from, from.getName) - case 1 => loadTemplateFile(indexFiles.head).copy(file = from) + val indexFiles = from.listFiles { file => file.getName == "index.md" || file.getName == "index.html" } + indexes match + case Nil => emptyTemplate(from, from.getName) + case Seq(loadedTemplate) => loadedTemplate.templateFile.copy(file = from) case _ => - val msg = s"ERROR: Multiple index pages found under ${from.toPath}" + // TODO (https://github.com/lampepfl/scala3doc/issues/238): provide proper error handling + val msg = s"ERROR: Multiple index pages for $from found in ${indexes.map(_.file)}" throw new java.lang.RuntimeException(msg) val templateFile = if (from.isDirectory) loadIndexPage() else loadTemplateFile(from) @@ -101,7 +100,15 @@ class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper], args: pageSettings.flatMap(_.get("date").collect{ case s: String => s}).getOrElse("1900-01-01") // blogs without date are last children.sortBy(dateFrom).reverse - Some(LoadedTemplate(templateFile, processedChildren.toList, from)) + val processedTemplate = // Set provided name as arg in page for `docs` + if from.getParentFile.toPath == docsPath && templateFile.isIndexPage() then + // TODO (https://github.com/lampepfl/scala3doc/issues/238): provide proper error handling + if templateFile.title != "index" then println(s"[WARN] title in $from will be overriden") + val projectTitle = args.projectTitle.getOrElse(args.name) + templateFile.copy(title = projectTitle) + else templateFile + + Some(LoadedTemplate(processedTemplate, processedChildren.toList, from)) catch case e: RuntimeException => // TODO (https://github.com/lampepfl/scala3doc/issues/238): provide proper error handling diff --git a/scala3doc/src/dotty/dokka/site/common.scala b/scala3doc/src/dotty/dokka/site/common.scala index 6f2936d8959e..5fa9b8493f5a 100644 --- a/scala3doc/src/dotty/dokka/site/common.scala +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -74,7 +74,7 @@ def loadTemplateFile(file: File): TemplateFile = { def getSettingValue(k: String, v: JList[String]): String | List[String] = if v.size == 1 then v.get(0) else v.asScala.toList - val globalKeys = Set("extraJS", "extraCSS", "layout", "hasFrame", "name") + val globalKeys = Set("extraJS", "extraCSS", "layout", "hasFrame", "name", "title") val allSettings = yamlCollector.getData.asScala.toMap.transform(getSettingValue) val (global, inner) = allSettings.partition((k,_) => globalKeys.contains(k)) val settings = Map("page" -> inner) diff --git a/scala3doc/src/dotty/dokka/site/processors.scala b/scala3doc/src/dotty/dokka/site/processors.scala index 75163f9f5cef..b6a4bda1dc8b 100644 --- a/scala3doc/src/dotty/dokka/site/processors.scala +++ b/scala3doc/src/dotty/dokka/site/processors.scala @@ -100,7 +100,7 @@ class SitePagesCreator(ctx: Option[StaticSiteContext]) extends BaseStaticSitePro val rootContent = indexes.headOption.fold(ctx.asContent(Text(), mkDRI(extra = "root_content")).get(0))(_.getContent) val root = AContentPage( - input.getName, + ctx.args.projectTitle.getOrElse(ctx.args.name), (List(modifiedModuleRoot.modified("API", modifiedModuleRoot.getChildren)) ++ children).asJava, rootContent, JSet(docsDRI), diff --git a/scala3doc/src/dotty/dokka/transformers/PackageHierarchyTransformer.scala b/scala3doc/src/dotty/dokka/transformers/PackageHierarchyTransformer.scala index f5af0caf5aea..377656e670b7 100644 --- a/scala3doc/src/dotty/dokka/transformers/PackageHierarchyTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/PackageHierarchyTransformer.scala @@ -63,12 +63,9 @@ class PackageHierarchyTransformer(context: DokkaContext) extends PageTransformer val packagePagesWithTokens = packagePages.map(page => (("""\.""".r.split(page.getName)).toSeq, page)) - val maxDepthElem = packagePagesWithTokens.maxBy( (tokens, page) => tokens.size ) - - page.modified( - page.getName, + val newPages = if packagePagesWithTokens.isEmpty then page.getChildren else + val maxDepthElem = packagePagesWithTokens.maxBy( (tokens, page) => tokens.size ) (otherPages ++ buildPackageTree(maxDepthElem(0).size, packagePagesWithTokens, Seq.empty)).asJava - ) - + page.modified(page.getName, newPages) } diff --git a/scala3doc/test-documentations/basic/docs/Adoc.md b/scala3doc/test-documentations/basic/docs/Adoc.md new file mode 100644 index 000000000000..f9e75f26fb07 --- /dev/null +++ b/scala3doc/test-documentations/basic/docs/Adoc.md @@ -0,0 +1,6 @@ +--- +title: Adoc +--- +# Header in Adoc + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/basic/docs/dir/index.md b/scala3doc/test-documentations/basic/docs/dir/index.md new file mode 100644 index 000000000000..40309d387819 --- /dev/null +++ b/scala3doc/test-documentations/basic/docs/dir/index.md @@ -0,0 +1,6 @@ +--- +title: A directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/basic/docs/dir/nested.md b/scala3doc/test-documentations/basic/docs/dir/nested.md new file mode 100644 index 000000000000..3e0ab5922afe --- /dev/null +++ b/scala3doc/test-documentations/basic/docs/dir/nested.md @@ -0,0 +1,6 @@ +--- +title: Nested in a directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/basic/docs/index.md b/scala3doc/test-documentations/basic/docs/index.md new file mode 100644 index 000000000000..5cdd253040db --- /dev/null +++ b/scala3doc/test-documentations/basic/docs/index.md @@ -0,0 +1,3 @@ +# {{ page.title }} in header + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/basic/images/basic.svg b/scala3doc/test-documentations/basic/images/basic.svg new file mode 100644 index 000000000000..1fb642c8bfa0 --- /dev/null +++ b/scala3doc/test-documentations/basic/images/basic.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scala3doc/test-documentations/basic/index.md b/scala3doc/test-documentations/basic/index.md new file mode 100644 index 000000000000..705503bfeb5b --- /dev/null +++ b/scala3doc/test-documentations/basic/index.md @@ -0,0 +1,6 @@ +--- +title: Basic test +--- +# Header + +And a text! \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala new file mode 100644 index 000000000000..a1c125a37bb8 --- /dev/null +++ b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala @@ -0,0 +1,75 @@ +package dotty.dokka +package site + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import org.junit.Test +import org.junit.Assert._ +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import java.nio.charset.Charset + + +class SiteGeneratationTest: + val projectName = "Test Project Name" + val projectTitle = "Test Project Title" + val projectVersion = "1.0.1-M1" + + private def withGeneratedSite(base: Path)(op: Path => Unit) = + val dest = Files.createTempDirectory("test-doc") + try + val args = Args( + name = projectName, + tastyRoots = Nil , + classpath = System.getProperty("java.class.path"), + output = dest.toFile, + docsRoot = Some(base.toAbsolutePath.toString), + projectVersion = Some(projectVersion), + projectTitle = Some(projectTitle) + ) + Main.main(args) + op(dest) + + finally println(dest.toFile) // IO.delete(dest.toFile) + + val testDocPath = Paths.get(BuildInfo.testDocumentationRoot) + + class DocumentContext(d: Document, path: Path): + import collection.JavaConverters._ + + def niceMsg(msg: String) = s"$msg in $path (body):\n ${d.html()}:\n" + + def assertTextsIn(selector: String, expected: String*) = + assertFalse(niceMsg("Selector not found"), d.select(selector).isEmpty) + val found = d.select(selector).eachText.asScala + assertEquals(niceMsg(s"Context does not match for '$selector'"), expected.toList, found.toList) + + def withHtmlFile(path: Path)(op: DocumentContext => Unit) = { + assertTrue(s"File at $path does not exisits!", Files.exists(path)) + val content = new String(Files.readAllBytes(path), Charset.defaultCharset()) + val document = Jsoup.parse(content) + op(DocumentContext(document, path)) + } + + @Test + def basicTest() = withGeneratedSite(testDocPath.resolve("basic")){ dest => + + def checkFile(path: String)(title: String, header: String, parents: Seq[String] = Nil) = + withHtmlFile(dest.resolve(path)){ content => + content.assertTextsIn(".projectName", projectName) + content.assertTextsIn(".projectVersion", projectVersion) + content.assertTextsIn("h1", header) + content.assertTextsIn("title", title) + content.assertTextsIn(".breadcrumbs a", (parents :+ title):_*) + } + + checkFile("index.html")(title = "Basic test", header = "Header") + checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectTitle)) + checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectTitle)) + checkFile("docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectTitle)) + checkFile("docs/dir/nested.html")( + title = "Nested in a directory", header = "Nested in a directory", parents = Seq(projectTitle, "A directory")) + + checkFile("docs/index.html")(title = projectTitle, header = s"$projectTitle in header") + } \ No newline at end of file