Skip to content

Commit

Permalink
Link titles for scaladoc/javadoc (#376)
Browse files Browse the repository at this point in the history
* Show the FQCN as link title on source directives.
* Overwrite a link title with the `title` attribute on `@link`

* Update Scripted tests
  • Loading branch information
ennru authored Oct 7, 2019
1 parent ed66f83 commit 57ce487
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 41 deletions.
34 changes: 30 additions & 4 deletions core/src/main/scala/com/lightbend/paradox/markdown/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,20 @@ abstract class ContainerBlockDirective(val names: String*) extends Directive {
/**
* Directives with defined "source" semantics.
*/
trait SourceDirective { this: Directive =>
trait SourceDirective {
this: Directive =>
def ctx: Writer.Context

final def page: Page = ctx.location.tree.label

final def variables: Map[String, String] = ctx.properties

protected def resolvedSource(node: DirectiveNode, page: Page): String = {
protected def title(node: DirectiveNode, page: Page): String = ""

protected def resolvedSource(node: DirectiveNode, page: Page): String =
extractLink(node, page)

protected def extractLink(node: DirectiveNode, page: Page): String = {
def ref(key: String) =
referenceMap.get(key.filterNot(_.isWhitespace).toLowerCase).map(_.getUrl).getOrElse {
ctx.error(s"Undefined reference key [$key]", node)
Expand Down Expand Up @@ -207,7 +215,7 @@ abstract class ExternalLinkDirective(names: String*)
def resolveLink(node: DirectiveNode, location: String): Url

def render(node: DirectiveNode, visitor: Visitor, printer: Printer): Unit =
new ExpLinkNode("", resolvedSource(node, page), node.contentsNode).accept(visitor)
new ExpLinkNode(title(node, page), resolvedSource(node, page), node.contentsNode).accept(visitor)

override protected def resolvedSource(node: DirectiveNode, page: Page): String = {
val link = super.resolvedSource(node, page)
Expand Down Expand Up @@ -271,6 +279,23 @@ abstract class ApiDocDirective(name: String)
case (property @ ApiDocProperty(pkg), url) => (pkg, PropertyUrl(property, variables.get))
}

override protected def title(node: DirectiveNode, page: Page): String = {
val link = extractLink(node, page)
try {
val url = Url(link)
val path = url.base.getPath
if (path.endsWith("$")) path.substring(0, path.length - 1)
else if (path.endsWith(".package")) path.substring(0, path.length - ".package".length)
else if (path.endsWith(".index")) path.substring(0, path.length - ".index".length)
else path
} catch {
case e @ Url.Error(reason) =>
ctx.logger.debug(e)
ctx.error(s"Failed to resolve [$link] because $reason", node)
""
}
}

def resolveLink(node: DirectiveNode, link: String): Url = {
val levels = link.split("[.]")
val packages = (1 to levels.init.size).map(levels.take(_).mkString("."))
Expand Down Expand Up @@ -474,7 +499,8 @@ case class FiddleDirective(ctx: Writer.Context)
val filterLabels = Directive.filterLabels("fiddle", node.attributes, labels, variables)
val (code, _) = Snippet(file, labels, filterLabels)

printer.println.print(s"""
printer.println.print(
s"""
<div data-scalafiddle="true" $params>
<pre class="prettyprint"><code class="language-scala">$code</code></pre>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ object Writer {
case n: ExpLinkNodeExtended =>
val rendering: LinkRenderer.Rendering = new LinkRenderer.Rendering(url, text)
val r2 =
if (StringUtils.isEmpty(title)) rendering
if (n.attributes.value("title") != null)
rendering.withAttribute("title", encode(n.attributes.value("title")))
else if (StringUtils.isEmpty(title)) rendering
else rendering.withAttribute("title", encode(title))
if (n.attributes.value("open", "same") == "new") {
r2
Expand Down
14 changes: 13 additions & 1 deletion docs/src/main/paradox/directives/linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Linking

#### External links

External links can be created with the default markdown syntax `[text](url)`. Additionally Paradox introduces `@link:` which accepts the `open=new` attribute to make the link open in a new browser tab (it adds `target="_blank" rel="noopener noreferrer"` to the anchor tag).
External links can be created with the default markdown syntax `[text](url)`. Additionally Paradox introduces `@link:` which accepts the `open=new` attribute to make the link open in a new browser tab (it adds `target="_blank" rel="noopener noreferrer"` to the anchor tag), and the `title` attribute to be used on the `a` tag.

```
See the @link:[Paradox GitHub repo](https://github.com/lightbend/paradox) { open=new } for more information.
Expand Down Expand Up @@ -83,6 +83,12 @@ URL.

The `@scaladoc` directive also supports site root relative base URLs using the `.../` syntax.

@@@ Note

The [sbt-paradox-apidoc](https://github.com/lightbend/sbt-paradox-apidoc) plugin creates `@scaladoc` and `@javadoc` API links by searching the class paths for the appropriate class to link to.

@@@

#### @javadoc directive

Use the `@javadoc` directives to link to Javadoc sites based on the package
Expand Down Expand Up @@ -112,6 +118,12 @@ associated with the `java.specification.version` system property.

The `@javadoc` directive also supports site root relative base URLs using the `.../` syntax.

@@@ Note

The [sbt-paradox-apidoc](https://github.com/lightbend/sbt-paradox-apidoc) plugin creates `@scaladoc` and `@javadoc` API links by searching the class paths for the appropriate class to link to.

@@@

#### @github directive

Use the `@github` directive to link to GitHub issues, commits and files.
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/sbt-test/paradox/parameterized-links/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ TaskKey[Unit]("checkJavadocJavalibContent") := {
assert(file.exists, s"${file.getAbsolutePath} did not exist")
val content = IO.readLines(file).mkString
assert(content.matches(
raw"""<p><a href="https://docs.oracle.com/javase/\d+/docs/api/\?java/io/File\.html#separator">File\.separator</a></p>"""))
raw"""<p><a href="https://docs.oracle.com/javase/\d+/docs/api/\?java/io/File\.html#separator" title="java.io.File">File\.separator</a></p>"""))
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<p><a href="http://doc.akka.io/japi/akka/2.4.10/?akka/actor/Actor.html">Actor</a></p>
<p><a href="http://doc.akka.io/japi/akka-http/10.0.0/?akka/http/javadsl/Http.html#shutdownAllConnectionPools--">Http</a></p>
<p><a href="https://api.example.com/java/?org/example/Server.html">Server</a></p>
<p><a href="http://doc.akka.io/japi/akka/2.4.10/?akka/actor/Actor.html" title="akka.actor.Actor">Actor</a></p>
<p><a href="http://doc.akka.io/japi/akka-http/10.0.0/?akka/http/javadsl/Http.html#shutdownAllConnectionPools--" title="akka.http.javadsl.Http">Http</a></p>
<p><a href="https://api.example.com/java/?org/example/Server.html" title="org.example.Server">Server</a></p>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<p>Use a <a href="http://www.scala-lang.org/api/2.11.12/scala/concurrent/Future.html">Future</a> to avoid that long running operations block the <a href="http://doc.akka.io/api/akka/2.4.10/akka/actor/Actor.html">Actor</a>.</p>
<p><a href="http://doc.akka.io/api/akka-http/10.0.0/akka/http/scaladsl/Http$.html">Http</a></p>
<p><a href="https://example.org/api/0.1.0/org/example/Server.html#start():Unit">Server#start</a></p>
<p>Use a <a href="http://www.scala-lang.org/api/2.11.12/scala/concurrent/Future.html" title="scala.concurrent.Future">Future</a> to avoid that long running operations block the <a href="http://doc.akka.io/api/akka/2.4.10/akka/actor/Actor.html" title="akka.actor.Actor">Actor</a>.</p>
<p><a href="http://doc.akka.io/api/akka-http/10.0.0/akka/http/scaladsl/Http$.html" title="akka.http.scaladsl.Http">Http</a></p>
<p><a href="https://example.org/api/0.1.0/org/example/Server.html#start():Unit" title="org.example.Server">Server#start</a></p>
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<p>Use a <a href="http://www.scala-lang.org/api/2.12.1/scala/concurrent/Future.html">Future</a> to avoid that long running operations block the <a href="http://doc.akka.io/api/akka/2.4.13/akka/actor/Actor.html">Actor</a>.</p>
<p><a href="http://doc.akka.io/api/akka-http/10.0.0/akka/http/scaladsl/Http$.html">Http</a></p>
<p>Use a <a href="http://www.scala-lang.org/api/2.12.1/scala/concurrent/Future.html" title="scala.concurrent.Future">Future</a> to avoid that long running operations block the <a href="http://doc.akka.io/api/akka/2.4.13/akka/actor/Actor.html" title="akka.actor.Actor">Actor</a>.</p>
<p><a href="http://doc.akka.io/api/akka-http/10.0.0/akka/http/scaladsl/Http$.html" title="akka.http.scaladsl.Http">Http</a></p>
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,39 @@ class JavadocDirectiveSpec extends MarkdownBaseSpec {

"javadoc directive" should "create links using configured URL templates" in {
markdown("@javadoc[Publisher](org.reactivestreams.Publisher)") shouldEqual
html("""<p><a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html">Publisher</a></p>""")
html("""<p><a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html" title="org.reactivestreams.Publisher">Publisher</a></p>""")
}

it should "support 'javadoc:' as an alternative name" in {
markdown("@javadoc:[Publisher](org.reactivestreams.Publisher)") shouldEqual
html("""<p><a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html">Publisher</a></p>""")
html("""<p><a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html" title="org.reactivestreams.Publisher">Publisher</a></p>""")
}

it should "support root relative '...' base urls" in {
markdown("@javadoc[Url](root.relative.Url)") shouldEqual
html("""<p><a href="javadoc/api/?root/relative/Url.html">Url</a></p>""")
html("""<p><a href="javadoc/api/?root/relative/Url.html" title="root.relative.Url">Url</a></p>""")
}

it should "handle method links correctly" in {
markdown("@javadoc[File.pathSeparator](java.io.File#pathSeparator)") shouldEqual
html("""<p><a href="https://docs.oracle.com/javase/8/docs/api/?java/io/File.html#pathSeparator">File.pathSeparator</a></p>""")
html("""<p><a href="https://docs.oracle.com/javase/8/docs/api/?java/io/File.html#pathSeparator" title="java.io.File">File.pathSeparator</a></p>""")
}

it should "handle class links correctly" in {
markdown("@javadoc[Http](akka.http.javadsl.Http)") shouldEqual
html("""<p><a href="http://doc.akka.io/japi/akka-http/10.0.0/index.html?akka/http/javadsl/Http.html">Http</a></p>""")
html("""<p><a href="http://doc.akka.io/japi/akka-http/10.0.0/index.html?akka/http/javadsl/Http.html" title="akka.http.javadsl.Http">Http</a></p>""")
markdown("@javadoc[Actor](akka.actor.Actor)") shouldEqual
html("""<p><a href="http://doc.akka.io/japi/akka/2.4.10/?akka/actor/Actor.html">Actor</a></p>""")
html("""<p><a href="http://doc.akka.io/japi/akka/2.4.10/?akka/actor/Actor.html" title="akka.actor.Actor">Actor</a></p>""")
}

it should "retain whitespace before or after" in {
markdown("The @javadoc:[Actor](akka.actor.Actor) class") shouldEqual
html("""<p>The <a href="http://doc.akka.io/japi/akka/2.4.10/?akka/actor/Actor.html">Actor</a> class</p>""")
html("""<p>The <a href="http://doc.akka.io/japi/akka/2.4.10/?akka/actor/Actor.html" title="akka.actor.Actor">Actor</a> class</p>""")
}

it should "parse but ignore directive attributes" in {
markdown("The @javadoc:[Publisher](org.reactivestreams.Publisher) { .javadoc a=1 } spec") shouldEqual
html("""<p>The <a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html">Publisher</a> spec</p>""")
html("""<p>The <a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html" title="org.reactivestreams.Publisher">Publisher</a> spec</p>""")
}

it should "throw exceptions for unconfigured default base URL" in {
Expand All @@ -91,13 +91,13 @@ class JavadocDirectiveSpec extends MarkdownBaseSpec {
|
| [Publisher]: org.reactivestreams.Publisher
""".stripMargin) shouldEqual
html("""<p>The <a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html">Publisher</a> spec</p>""")
html("""<p>The <a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/?org/reactivestreams/Publisher.html" title="org.reactivestreams.Publisher">Publisher</a> spec</p>""")
}

it should "support creating non frame style links" in {
val ctx = context.andThen(c => c.copy(properties = c.properties.updated("javadoc.link_style", "direct")))
markdown("@javadoc[Publisher](org.reactivestreams.Publisher)")(ctx) shouldEqual
html("""<p><a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Publisher.html">Publisher</a></p>""")
html("""<p><a href="http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Publisher.html" title="org.reactivestreams.Publisher">Publisher</a></p>""")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ class LinkDirectiveSpec extends MarkdownBaseSpec {
testHtml("""<p>This <a href="page.pdf" title="Page" target="_blank" rel="noopener noreferrer">Page</a> is linked.</p>""")
}

it should "use overwrite the title with the `title` attribute" in {
testMarkdown("""This @link:[Page](page.pdf) { title="Link to Page" } is linked.""") shouldEqual
testHtml("""<p>This <a href="page.pdf" title="Link to Page">Page</a> is linked.</p>""")
}

it should "use the `open` and title attributes" in {
testMarkdown("""This @link:[Page](page.pdf) { open=new title="Link to Page" } is linked.""") shouldEqual
testHtml("""<p>This <a href="page.pdf" title="Link to Page" target="_blank" rel="noopener noreferrer">Page</a> is linked.</p>""")
}

it should "support referenced links with implicit key" in {
testMarkdown(
"""This @link:[SBT] { .ref a=1 } is linked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,45 +31,45 @@ class ScaladocDirectiveSpec extends MarkdownBaseSpec {

"Scaladoc directive" should "create links using configured URL templates" in {
markdown("@scaladoc[Model](org.example.Model)") shouldEqual
html("""<p><a href="http://example.org/api/0.1.2/org/example/Model.html">Model</a></p>""")
html("""<p><a href="http://example.org/api/0.1.2/org/example/Model.html" title="org.example.Model">Model</a></p>""")
}

it should "support 'scaladoc:' as an alternative name" in {
markdown("@scaladoc:[Model](org.example.Model)") shouldEqual
html("""<p><a href="http://example.org/api/0.1.2/org/example/Model.html">Model</a></p>""")
html("""<p><a href="http://example.org/api/0.1.2/org/example/Model.html" title="org.example.Model">Model</a></p>""")
}

it should "support root relative '...' base urls" in {
markdown("@scaladoc:[Url](root.relative.Url)") shouldEqual
html("""<p><a href="scaladoc/api/root/relative/Url.html">Url</a></p>""")
html("""<p><a href="scaladoc/api/root/relative/Url.html" title="root.relative.Url">Url</a></p>""")
}

it should "handle method links correctly" in {
markdown("@scaladoc[???](scala.Predef$#???:Nothing)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/Predef$.html#???:Nothing">???</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/Predef$.html#???:Nothing" title="scala.Predef">???</a></p>""")

markdown(
"""@scaladoc:[Actor#preStart]
|
| [Actor#preStart]: akka.actor.Actor#preStart():Unit""") shouldEqual
html("""<p><a href="http://doc.akka.io/api/akka/2.4.10/akka/actor/Actor.html#preStart():Unit">Actor#preStart</a></p>""")
html("""<p><a href="http://doc.akka.io/api/akka/2.4.10/akka/actor/Actor.html#preStart():Unit" title="akka.actor.Actor">Actor#preStart</a></p>""")
}

it should "handle object links correctly" in {
markdown("@scaladoc[Http](akka.http.scaladsl.Http$)") shouldEqual
html("""<p><a href="http://doc.akka.io/api/akka-http/10.0.0/akka/http/scaladsl/Http$.html">Http</a></p>""")
html("""<p><a href="http://doc.akka.io/api/akka-http/10.0.0/akka/http/scaladsl/Http$.html" title="akka.http.scaladsl.Http">Http</a></p>""")
markdown("@scaladoc[Actor](akka.actor.Actor)") shouldEqual
html("""<p><a href="http://doc.akka.io/api/akka/2.4.10/akka/actor/Actor.html">Actor</a></p>""")
html("""<p><a href="http://doc.akka.io/api/akka/2.4.10/akka/actor/Actor.html" title="akka.actor.Actor">Actor</a></p>""")
}

it should "retain whitespace before or after" in {
markdown("The @scaladoc:[Model](org.example.Model) class") shouldEqual
html("""<p>The <a href="http://example.org/api/0.1.2/org/example/Model.html">Model</a> class</p>""")
html("""<p>The <a href="http://example.org/api/0.1.2/org/example/Model.html" title="org.example.Model">Model</a> class</p>""")
}

it should "parse but ignore directive attributes" in {
markdown("The @scaladoc:[Model](org.example.Model) { .scaladoc a=1 } spec") shouldEqual
html("""<p>The <a href="http://example.org/api/0.1.2/org/example/Model.html">Model</a> spec</p>""")
html("""<p>The <a href="http://example.org/api/0.1.2/org/example/Model.html" title="org.example.Model">Model</a> spec</p>""")
}

it should "throw exceptions for unconfigured default base URL" in {
Expand All @@ -90,11 +90,11 @@ class ScaladocDirectiveSpec extends MarkdownBaseSpec {
"scaladoc.version" -> "2.12.0")

markdown("@scaladoc[Int](scala.Int)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.12.0/scala/Int.html">Int</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.12.0/scala/Int.html" title="scala.Int">Int</a></p>""")
markdown("@scaladoc[Codec$](scala.io.Codec$)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.12.0/scala/io/Codec$.html">Codec$</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.12.0/scala/io/Codec$.html" title="scala.io.Codec">Codec$</a></p>""")
markdown("@scaladoc[scala.io package](scala.io.index)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.12.0/scala/io/index.html">scala.io package</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.12.0/scala/io/index.html" title="scala.io">scala.io package</a></p>""")
}

it should "support Scala 2.11 links" in {
Expand All @@ -103,11 +103,11 @@ class ScaladocDirectiveSpec extends MarkdownBaseSpec {
"scaladoc.version" -> "2.11.12")

markdown("@scaladoc[Int](scala.Int)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/Int.html">Int</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/Int.html" title="scala.Int">Int</a></p>""")
markdown("@scaladoc[Codec$](scala.io.Codec$)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/io/Codec$.html">Codec$</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/io/Codec$.html" title="scala.io.Codec">Codec$</a></p>""")
markdown("@scaladoc[scala.io package](scala.io.package)") shouldEqual
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/io/package.html">scala.io package</a></p>""")
html("""<p><a href="http://www.scala-lang.org/api/2.11.12/scala/io/package.html" title="scala.io">scala.io package</a></p>""")
}

it should "support referenced links" in {
Expand All @@ -116,6 +116,6 @@ class ScaladocDirectiveSpec extends MarkdownBaseSpec {
|
| [1]: org.example.Model
""") shouldEqual
html("""<p>The <a href="http://example.org/api/0.1.2/org/example/Model.html">Model</a> spec</p>""")
html("""<p>The <a href="http://example.org/api/0.1.2/org/example/Model.html" title="org.example.Model">Model</a> spec</p>""")
}
}

0 comments on commit 57ce487

Please sign in to comment.