Skip to content

Commit bbbcfde

Browse files
authored
Merge pull request #10401 from romanowski/scala3doc/our-doc
Provide basic documentation for scala3doc
2 parents e0dae95 + 93a87b0 commit bbbcfde

15 files changed

+334
-26
lines changed

docs/docs/usage/dottydoc.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
layout: doc-page
3-
title: Dottydoc
3+
title: Dottydoc [Legacy]
44
---
55

66
Dottydoc is a tool to generate a combined documentation and API reference for

docs/docs/usage/scala3doc/blog.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Built-in blog
3+
---
4+
5+
# {{page.title}}
6+
7+
Scala3doc allows you to include a simple blog in your documentation. For now, it
8+
provides only basic features. In the future, we plan to include more advanced
9+
features like tagging or author pages.
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
title: API Documentation
3+
---
4+
5+
# {{ page.title }}
6+
7+
Scala3doc's main feature is creating API documentation from code comments.
8+
9+
By default, the code comments are understood as Markdown, though we also support
10+
Scaladoc's old [Wiki syntax](https://docs.scala-lang.org/style/scaladoc.html).
11+
12+
## Syntax
13+
14+
### Definition links
15+
16+
Our definition link syntax is quite close to Scaladoc's syntax, though we have made some
17+
quality-of-life improvements.
18+
19+
#### Basic syntax
20+
21+
A definition link looks as follows: `[[scala.collection.immutable.List]]`.
22+
23+
Which is to say, a definition link is a sequence of identifiers separated by
24+
`.`. The identifiers can be separated with `#` as well for Scaladoc compatibility.
25+
26+
By default, an identifier `id` references the first (in source order) entity
27+
named `id`. An identifier can end with `$`, which forces it to refer to a value
28+
(an object, a value, a given); an identifier can also end with `!`, which forces
29+
it to refer to a type (a class, a type alias, a type member).
30+
31+
The links are resolved relative to the current location in source. That is, when
32+
documenting a class, the links are relative to the entity enclosing the class (a
33+
package, a class, an object); the same applies to documenting definitions.
34+
35+
Special characters in links can be backslash-escaped, which makes them part of
36+
identifiers instead. For example, `` [[scala.collection.immutable\.List]] ``
37+
references the class named `` `immutable.List` `` in package `scala.collection`.
38+
39+
#### New syntax
40+
41+
We have extended Scaladoc definition links to make them a bit more pleasant to
42+
write and read in source. The aim was also to bring the link and Scala syntax
43+
closer together. The new features are:
44+
45+
1. `package` can be used as a prefix to reference the enclosing package
46+
Example:
47+
```
48+
package utils
49+
class C {
50+
def foo = "foo".
51+
}
52+
/** See also [[package.C]]. */
53+
class D {
54+
def bar = "bar".
55+
}
56+
```
57+
The `package` keyword helps make links to the enclosing package shorter
58+
and a bit more resistant to name refactorings.
59+
1. `this` can be used as a prefix to reference the enclosing classlike
60+
Example:
61+
```
62+
class C {
63+
def foo = "foo"
64+
/** This is not [[this.foo]], this is bar. */
65+
def bar = "bar"
66+
}
67+
```
68+
Using a Scala keyword here helps make the links more familiar, as well as
69+
helps the links survive class name changes.
70+
1. Backticks can be used to escape identifiers
71+
Example:
72+
```
73+
def `([.abusive.])` = ???
74+
/** TODO: Figure out what [[`([.abusive.])`]] is. */
75+
def foo = `([.abusive.])`
76+
```
77+
Scaladoc required backslash-escaping to reference such identifiers. Instead,
78+
Scala3doc allows using the familiar Scala backtick quotation.
79+
80+
#### Why keep the Wiki syntax for links?
81+
82+
There are a few reasons why we've kept the Wiki syntax for documentation links
83+
instead of reusing the Markdown syntax. Those are:
84+
85+
1. Nameless links in Markdown are ugly: `[](definition)` vs `[[definition]]`
86+
By far, most links in documentation are nameless. It should be obvious how to
87+
write them.
88+
2. Local member lookup collides with URL fragments: `[](#field)` vs `[[#field]]`
89+
3. Overload resolution collides with MD syntax: `[](meth(Int))` vs `[[meth(Int)]]`
90+
4. Now that we have a parser for the link syntax, we can allow spaces inside (in
91+
Scaladoc one needed to slash-escape those), but that doesn't get recognized
92+
as a link in Markdown: `[](meth(Int, Float))` vs `[[meth(Int, Float)]]`
93+
94+
None of these make it completely impossible to use the standard Markdown link
95+
syntax, but they make it much more awkward and ugly than it needs to be. On top
96+
of that, Markdown link syntax doesn't even save any characters.

docs/docs/usage/scala3doc/index.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
title: Scala3doc
3+
---
4+
5+
![Scala3doc logo](/images/scala3doc-logo.png)
6+
7+
Scala3doc is tool to generate documentation for your Scala 3 projects. It provies similar features to `javadoc` or `scaladoc` as well as `jekyll` or `docusaurus`.
8+
9+
As you probably have guessed, this whole site was created using Scala3doc.
10+
11+
12+
{% for post in site.posts %}
13+
## [{{ post.title }}](/{{ post.url }})
14+
15+
{{ post.excerpt }}
16+
17+
[ (read more) ](/{{ post.url }})
18+
19+
{% endfor %}
20+
21+
## Other extensions
22+
23+
We would love to have your feedback on what you think would be good in order to
24+
render the documentation you want! Perhaps you would like to render method
25+
definitions or members? Do you want to have runnable code snippets? Let us know
26+
by filing [issues](https://github.com/lampepfl/dotty/issues/new)!
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
title: Scala3doc-specific Tags and Features
3+
---
4+
5+
# {{page.title}}
6+
7+
Scala3doc extends Markdown with additional features, such as linking
8+
to API definitions. This can be used from within static documentation and blog
9+
posts to provide blend-in content.
10+
11+
## Linking to API
12+
13+
Scala3doc allows linking to API documentation with Wiki-style links. Linking to
14+
`scala.collection.immutable.List` is as simple as
15+
`[[scala.collection.immutable.List]]`. For more information on the exact syntax, see [doc comment documentation](./docComments.html#definition-links).
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
title: Static docucmentation
3+
---
4+
5+
# {{ page.title}}
6+
7+
Scala3doc is able to generate static sites, known from [Jekyll](http://jekyllrb.com/) or [Docusaurus](https://docusaurus.io/).
8+
Having a combined tool allows to provide interaction between static documentation and API, thus allowing the two to blend naturally.
9+
10+
Creating a site is just as simple as in Jekyll. The site root contains the
11+
layout of the site and all files placed there will be either considered static,
12+
or processed for template expansion.
13+
14+
The files that are considered for template expansion must end in `*.{html,md}`
15+
and will from here on be referred to as "template files" or "templates".
16+
17+
A simple "hello world" site could look something like this:
18+
19+
```
20+
├── docs
21+
│ └── getting-started.md
22+
└── index.html
23+
```
24+
25+
This will give you a site with the following files in generated documentation:
26+
27+
```
28+
index.html
29+
docs/getting-started.html
30+
```
31+
32+
Scala3doc can transform both files and directories (to organize your documentation into tree-like structure). By default directories has title based on file name and has empty content. There is an option to include `index.html` or `index.md` (not both) to provide both content and properties like title (see [Properties](#properties)).
33+
34+
## Properties
35+
36+
Scala3doc uses the [Liquid](https://shopify.github.io/liquid/) templating engine
37+
and provides a number of custom filters and tags specific to Scala
38+
documentation.
39+
40+
In Scala3doc, all templates can contain YAML front-matter. The front-matter
41+
is parsed and put into the `page` variable available in templates via Liquid.
42+
43+
Scala3doc uses some predefined properties to controls some aspect of page.
44+
45+
Predefined properties:
46+
47+
- **title** provide page title that will be used in navigation and html metadata.
48+
- **extraCss** additional `.css` files that will be included in this page. Paths should be relative to documentation root. **This setting is not exported to template engine.**
49+
- **extraJs** additional `.js` files that will be included in this page. Paths should be relative to documentation root. **This setting is not exported to template engine.**
50+
- **hasFrame** when set to `false` page will not include default layout (navigation, breadcrumbs etc.) but only token html wrapper to provide metadata and resources (js and css files). **This setting is not exported to template engine.**
51+
- **layout** - predefined layout to use, see below. **This setting is not exported to template engine.**
52+
53+
54+
## Using existing Templates and Layouts
55+
56+
To perform template expansion, Dottydoc looks at the `layout` field in the front-matter.
57+
Here's a simple example of the templating system in action, `index.html`:
58+
59+
```html
60+
---
61+
layout: main
62+
---
63+
64+
<h1>Hello world!</h1>
65+
```
66+
67+
With a simple main template like this:
68+
69+
{% raw %}
70+
```html
71+
<html>
72+
<head>
73+
<title>Hello, world!</title>
74+
</head>
75+
<body>
76+
{{ content }}
77+
</body>
78+
</html>
79+
```
80+
81+
Would result in `{{ content }}` being replaced by `<h1>Hello world!</h1>` from
82+
the `index.html` file.
83+
{% endraw %}
84+
85+
Layouts must be placed in a `_layouts` directory in the site root:
86+
87+
```
88+
├── _layouts
89+
│ └── main.html
90+
├── docs
91+
│ └── getting-started.md
92+
└── index.html
93+
```
94+
95+
Sidebar
96+
=======
97+
Scala3doc by default uses layout of files in `docs` directory to create table of content. There is also ability to override it by providing a `sidebar.yml` file in the site root:
98+
99+
```yaml
100+
sidebar:
101+
- title: Blog
102+
url: blog/index.html
103+
- title: Docs
104+
url: docs/index.html
105+
- title: Usage
106+
subsection:
107+
- title: Dottydoc
108+
url: docs/usage/dottydoc.html
109+
- title: sbt-projects
110+
url: docs/usage/sbt-projects.html
111+
```
112+
113+
The `sidebar` key is mandatory, as well as `title` for each element. The
114+
default table of contents allows you to have subsections - albeit the current
115+
depth limit is 2 however it accepts both files and directories and latter can be used to provide deeper structures.
116+
117+
The items which have on the `subsection` level does not accepts `url`.
118+
119+
```
120+
├── blog
121+
│ └── _posts
122+
│ └── 2016-12-05-implicit-function-types.md
123+
├── index.html
124+
└── sidebar.yml
125+
```

docs/images/scala3doc-logo.png

25 KB
Loading

docs/sidebar.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ sidebar:
1515
url: docs/usage/language-versions.html
1616
- title: cbt-projects
1717
url: docs/usage/cbt-projects.html
18-
- title: Dottydoc
18+
- title: Scala3doc
19+
url: docs/usage/scala3doc
20+
- title: Dottydoc [Legacy]
1921
url: docs/usage/dottydoc.html
2022
- title: Reference
2123
subsection:

scala3doc/scala3-docs/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</a>
2121
</li>
2222
<li class="nav-item">
23-
<a class="nav-link" href="docs/blog/index.html">
23+
<a class="nav-link" href="blog/index.html">
2424
Blog
2525
</a>
2626
</li>

scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,9 @@ class DottyDokkaPlugin extends DokkaJavaPlugin:
168168
.overrideExtension(dokkaBase.getLocationProvider)
169169
)
170170

171-
extension (ctx: DokkaContext):
172-
def siteContext: Option[StaticSiteContext] = ctx.getConfiguration.asInstanceOf[DottyDokkaConfig].staticSiteContext
173-
def args: Args = ctx.getConfiguration.asInstanceOf[DottyDokkaConfig].docConfiguration.args
171+
extension (ctx: DokkaContext):
172+
def siteContext: Option[StaticSiteContext] = ctx.getConfiguration.asInstanceOf[DottyDokkaConfig].staticSiteContext
173+
def args: Args = ctx.getConfiguration.asInstanceOf[DottyDokkaConfig].docConfiguration.args
174174

175175
// TODO (https://github.com/lampepfl/scala3doc/issues/232): remove once problem is fixed in Dokka
176176
extension [T] (builder: ExtensionBuilder[T]):

scala3doc/src/dotty/dokka/site/LoadedTemplate.scala

+10-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,18 @@ case class LazyEntry(getKey: String, value: () => String) extends JMapEntry[Stri
2020
case class LoadedTemplate(templateFile: TemplateFile, children: List[LoadedTemplate], file: File):
2121

2222
private def brief(ctx: StaticSiteContext): String =
23-
val code = Jsoup.parse(resolveToHtml(ctx).code)
24-
code.select("p").first().outerHtml()
23+
try
24+
val code = Jsoup.parse(resolveToHtml(ctx).code)
25+
Option(code.select("p").first()).fold("...")(_.outerHtml())
26+
catch
27+
case e: Throwable =>
28+
// TODO (https://github.com/lampepfl/scala3doc/issues/238): provide proper error handling
29+
println(s"[ERROR] Unable to process brief for ${templateFile.file}")
30+
e.printStackTrace()
31+
"..."
2532

2633
def lazyTemplateProperties(ctx: StaticSiteContext): JMap[String, Object] = new java.util.AbstractMap[String, Object]():
27-
def entrySet(): JSet[JMapEntry[String, Object]] =
34+
lazy val entrySet: JSet[JMapEntry[String, Object]] =
2835
val site = templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]]
2936
site.asJava.entrySet() ++ JSet(
3037
LazyEntry("url", () => ctx.relativePath(LoadedTemplate.this).toString),

scala3doc/src/dotty/dokka/site/StaticSiteContext.scala

+17-8
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,28 @@ class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper], args:
4444

4545
lazy val mainPages: Seq[StaticPageNode] = templates.map(templateToPage)
4646

47+
val docsPath = root.toPath.resolve("docs")
48+
4749
lazy val allPages: Seq[StaticPageNode] = sideBarConfig.fold(mainPages){ sidebar =>
4850
def flattenPages(p: StaticPageNode): Set[Path] =
4951
Set(p.template.file.toPath) ++ p.getChildren.asScala.collect { case p: StaticPageNode => flattenPages(p) }.flatten
5052

5153
val mainFiles = mainPages.toSet.flatMap(flattenPages)
52-
val docsPath = root.toPath.resolve("docs")
54+
5355
val allPaths =
5456
if !Files.exists(docsPath) then Nil
5557
else Files.walk(docsPath, FileVisitOption.FOLLOW_LINKS).iterator().asScala.toList
5658

57-
val orphanedFiles = allPaths.filterNot(mainFiles.contains).filter { p =>
59+
val orphanedFiles = allPaths.filterNot { p =>
60+
def name = p.getFileName.toString
61+
def isMain = name == "index.html" || name == "index.md"
62+
mainFiles.contains(p) || (isMain && mainFiles.contains(p.getParent))
63+
}.filter { p =>
5864
val name = p.getFileName.toString
5965
name.endsWith(".md") || name.endsWith(".html")
6066
}
6167

6268
val orphanedTemplates = orphanedFiles.flatMap(p => loadTemplate(p.toFile, isBlog = false))
63-
6469
mainPages ++ orphanedTemplates.map(templateToPage)
6570
}
6671

@@ -114,13 +119,17 @@ class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper], args:
114119
private def loadSidebarContent(entry: Sidebar): LoadedTemplate = entry match
115120
case Sidebar.Page(title, url) =>
116121
val isBlog = title == "Blog"
117-
val path = if isBlog then "blog" else url.stripSuffix(".html") + ".md"
118-
val file = root.toPath.resolve(path) // Add support for .html files!
119-
val LoadedTemplate(template, children, tFile) = loadTemplate(file.toFile, isBlog).get // Add proper logging if file does not exisits
120-
LoadedTemplate(template.copy(settings = template.settings + ("title" -> title)), children, tFile)
122+
val path = if isBlog then "blog" else
123+
if Files.exists(root.toPath.resolve(url)) then url
124+
else url.stripSuffix(".html") + ".md"
125+
126+
val file = root.toPath.resolve(path).toFile
127+
val LoadedTemplate(template, children, _) = loadTemplate(file, isBlog).get // Add proper logging if file does not exisits
128+
LoadedTemplate(template.copy(settings = template.settings + ("title" -> title), file = file), children, file)
129+
121130
case Sidebar.Category(title, nested) =>
122131
// Add support for index.html/index.md files!
123-
val fakeFile = new File(root, title)
132+
val fakeFile = new File(new File(root, "docs"), title)
124133
LoadedTemplate(emptyTemplate(fakeFile, title), nested.map(loadSidebarContent), fakeFile)
125134

126135
private def loadAllFiles() =

0 commit comments

Comments
 (0)