Skip to content

Commit

Permalink
Add 'javadoc' inline macro extension
Browse files Browse the repository at this point in the history
Closes gh-14

Co-authored-by: Rob Winch <[email protected]>
  • Loading branch information
philwebb and rwinch committed Jul 8, 2024
1 parent c467e8a commit 5ef27ba
Show file tree
Hide file tree
Showing 4 changed files with 470 additions and 0 deletions.
95 changes: 95 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,101 @@ sectid-level-separator (default: .):: The character that must be used to separat

All IDs must be lowercase, must start with an alphabetic character, and may only contain alphanumeric characters aside from the aforementioned separators.

=== Javadoc

*require name:* @springio/asciidoctor-extensions/javadoc-extension

The javadoc extension allows you to quickly create links to javadoc sites.
For example, `javadoc:com.example.MyClass[]` will create a link to `xref:attachment$api/java/com/example/MyClass.html` with the text `MyClass`.

==== Syntax

The following format can be used when declaring a javadoc link:

----
[<location>]<class-reference>[#<anchor>]
----

Only `<class-reference>` is mandatory, and it must be the fully qualified name of the class to link to.
For example, `com.example.MyClass`.
References to inner-classes should use `$` notation instead of `.`.
For example, `com.example.MyClass$Builder`.

If a `<location>` is specified, it must end with `/`.

Any `<anchor>` must exactly match the anchor in the corresponding javadoc page.

==== Locations

Unless otherwise overridden, the default javadoc location will be `xref:attachment$api/java`.
If you want a different default, you can set the `javadoc-location` document attribute.
For example, you can set `javadoc-location` to `xref:api:java` if you publish javadoc under a `api` Antora module.

NOTE: document attributes can be set in your Antora playback under the https://docs.antora.org/antora/latest/playbook/asciidoc-attributes/#attributes-key[attributes key].

You can also override locations on a per-link basis.
For example:

[,asciidoc]
----
= Example
:url-jdk-javadoc: https://docs.oracle.com/en/java/javase/17/docs/api
Please read javadoc:{url-jdk-javadoc}/java.base/java.io.InputStream[]
----

==== Formats and Link Text

By default, a short form of the class name is used as the link text.
For example, if you link to `com.example.MyClass`, only `MyClass` is used for the link text.

If you want to change the format of the link text, you can use the `format` attribute.
For example, `javadoc:com.example.MyClass[format=full]` will use `com.example.MyClass` as the link text.

The following formats are supported:

[cols="1,1,3"]
|===
| Name | Description | Example

| `short` (default)
| The short class name
| `com.example.MyClass$Builder` -> `MyClass.Builder`

| `full`
| The fully-qualified class name
| `com.example.MyClass$Builder` -> `com.example.MyClass.Builder`

| `annotation`
| The short class name prefixed with `@`
| `com.example.MyAnnotation` -> `@MyAnnotation`
|===

TIP: You can change the default format by setting a `javadoc-format` document attribute.
This can be done site-wide, or on a page-by-page basis.

You can also specify directly specify link text if the existing formats are not suitable:

[,asciidoc]
----
See javadoc:com.example.MyClass$Builder[Builder] for details.
----

NOTE: The link text is _always_ wrapped in a `<code>` block so you should not use backticks.

==== Anchors

Anchors may be used to link to a specific part of a javadoc page.
The anchor link must exactly match the link in the target page.
For example, `javadoc:com.example.MyClass#myMethod(java.lang.String)`

When an anchor is specified, the link text will include a readable version.
The example above would render the link text `MyClass.myMethod(String)`.

==== UI Support

All anchors created using the `javadoc` macro will have a role of `apiref` to allow the UI to style them appropriately.

ifndef::env-npm[]
== Development Quickstart

Expand Down
95 changes: 95 additions & 0 deletions lib/javadoc-extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict'

const toProc = require('./util/to-proc')

const METHOD_REGEX = /(.*)\((.*)\)/

function register (registry, context) {
if (!(registry && context)) return // NOTE only works as scoped extension for now
registry.$groups().$store('springio/javadoc', toProc(createExtensionGroup(context)))
return registry
}

function createExtensionGroup () {
return function () {
this.inlineMacro(function () {
this.named('javadoc')
this.process((parent, target, attrs) => {
const text = process(parent.getDocument(), parseTarget(target), attrs)
return this.createInline(parent, 'quoted', text, {
type: 'monospaced',
})
})
})
}
}

function parseTarget (target) {
target = target.replaceAll('&#8230;&#8203;', '...')
const lastSlash = target.lastIndexOf('/')
const location = lastSlash !== -1 ? target.substring(0, lastSlash) : undefined
const reference = lastSlash !== -1 ? target.substring(lastSlash + 1, target.length) : target
const lastHash = reference.lastIndexOf('#')
const classReference = lastHash !== -1 ? reference.substring(0, lastHash) : reference
const anchor = lastHash !== -1 ? reference.substring(lastHash + 1, reference.length) : undefined
return { location, classReference, anchor }
}

function process (document, target, attrs) {
const location = target.location || document.getAttribute('javadoc-location', 'xref:attachment$api/java')
const format = attrs.format || document.getAttribute('javadoc-format', 'short')
const linkLocation = link(location, target)
const linkDescription = applyFormat(target, format, attrs.$positional)
return `${linkLocation}['${linkDescription}',role=apiref]`
}

function link (location, target) {
let link = location
link = !link.endsWith('/') ? link + '/' : link
link += target.classReference.replaceAll('.', '/').replaceAll('$', '.') + '.html'
if (target.anchor) link += '#' + target.anchor
return link
}

function applyFormat (target, format, positionalAttrs, annotationAnchor) {
if (positionalAttrs?.[0]) return positionalAttrs?.[0]
switch (format) {
case 'full':
return className(target.classReference, 'full') + anchorText(target.anchor, format, annotationAnchor)
case 'annotation':
return '@' + className(target.classReference, 'short') + anchorText(target.anchor, format)
default:
return className(target.classReference, 'short') + anchorText(target.anchor, format)
}
}

function anchorText (anchor, format, annotationAnchor) {
if (!anchor) return ''
if (format === 'annotation' || annotationAnchor) anchor = anchor.replaceAll('()', '')
const methodMatch = METHOD_REGEX.exec(anchor)
if (methodMatch) {
return '.' + methodText(methodMatch[1], methodMatch[2], format)
}
return '.' + anchor
}

function methodText (name, params, format) {
const varargs = params.endsWith('...')
if (varargs) params = params.substring(0, params.length - 3)
return (
name +
'(' +
params
.split(',')
.map((name) => className(name, format))
.join(', ') +
(!varargs ? ')' : '...)')
)
}

function className (classReference, format) {
if (format !== 'full') classReference = classReference.split('.').slice(-1)[0]
return classReference.replaceAll('$', '.')
}

module.exports = { register, createExtensionGroup }
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"./code-folding-extension": "./lib/code-folding-extension.js",
"./configuration-properties-extension": "./lib/configuration-properties-extension.js",
"./include-code-extension": "./lib/include-code-extension.js",
"./javadoc-extension": "./lib/javadoc-extension.js",
"./section-ids-extension": "./lib/section-ids-extension.js"
},
"imports": {
Expand Down
Loading

0 comments on commit 5ef27ba

Please sign in to comment.