/**
* Copyright 2013-2015 John Ericksen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.asciidoctor.asciidoclet;
import com.google.common.base.Optional;
import com.sun.javadoc.Doc;
import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.ParamTag;
import com.sun.javadoc.Tag;
import org.asciidoctor.*;
import static org.asciidoctor.Asciidoctor.Factory.create;
/**
* Doclet renderer using and configuring Asciidoctor.
*
* @author John Ericksen
*/
public class AsciidoctorRenderer implements DocletRenderer {
private static AttributesBuilder defaultAttributes() {
return AttributesBuilder.attributes()
.attribute("at", "@")
.attribute("slash", "/")
.attribute("icons", null)
.attribute("idprefix", "")
.attribute("idseparator", "-")
.attribute("javadoc", "")
.attribute("showtitle", true)
.attribute("source-highlighter", "coderay")
.attribute("coderay-css", "class")
.attribute("env-asciidoclet")
.attribute("env", "asciidoclet");
}
private static OptionsBuilder defaultOptions() {
return OptionsBuilder.options()
.safe(SafeMode.SAFE)
.backend("html5");
}
protected static final String INLINE_DOCTYPE = "inline";
private final Asciidoctor asciidoctor;
private final Optional<OutputTemplates> templates;
private final Options options;
public AsciidoctorRenderer(DocletOptions docletOptions, DocErrorReporter errorReporter) {
this(docletOptions, errorReporter, OutputTemplates.create(errorReporter), create(docletOptions.gemPath()));
}
/**
* Constructor used directly for testing purposes only.
*/
protected AsciidoctorRenderer(DocletOptions docletOptions, DocErrorReporter errorReporter, Optional<OutputTemplates> templates, Asciidoctor asciidoctor) {
this.asciidoctor = asciidoctor;
this.templates = templates;
this.options = buildOptions(docletOptions, errorReporter);
}
private Options buildOptions(DocletOptions docletOptions, DocErrorReporter errorReporter) {
OptionsBuilder opts = defaultOptions();
if (docletOptions.baseDir().isPresent()) {
opts.baseDir(docletOptions.baseDir().get());
}
if (templates.isPresent()) {
opts.templateDir(templates.get().templateDir());
}
opts.attributes(buildAttributes(docletOptions, errorReporter));
if (docletOptions.requires().size() > 0) {
for (String require : docletOptions.requires()) {
asciidoctor.rubyExtensionRegistry().requireLibrary(require);
}
}
return opts.get();
}
private Attributes buildAttributes(DocletOptions docletOptions, DocErrorReporter errorReporter) {
return defaultAttributes()
.attributes(new AttributesLoader(asciidoctor, docletOptions, errorReporter).load())
.get();
}
/**
* Renders a generic document (class, field, method, etc)
*
* @param doc input
*/
@Override
public void renderDoc(Doc doc) {
// hide text that looks like tags (such as annotations in source code) from Javadoc
doc.setRawCommentText(doc.getRawCommentText().replaceAll("@([A-Z])", "{@literal @}$1"));
StringBuilder buffer = new StringBuilder();
buffer.append(render(doc.commentText(), false));
buffer.append('\n');
for (Tag tag : doc.tags()) {
renderTag(tag, buffer);
buffer.append('\n');
}
doc.setRawCommentText(buffer.toString());
}
public void cleanup() {
if (templates.isPresent()) {
templates.get().delete();
}
}
/**
* Renders a document tag in the standard way.
*
* @param tag input
* @param buffer output buffer
*/
private void renderTag(Tag tag, StringBuilder buffer) {
buffer.append(tag.name()).append(' ');
// Special handling for @param <T> tags
// See http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#@param
if ((tag instanceof ParamTag) && ((ParamTag) tag).isTypeParameter()) {
ParamTag paramTag = (ParamTag) tag;
buffer.append("<" + paramTag.parameterName() + ">");
String text = paramTag.parameterComment();
if (text.length() > 0) {
buffer.append(' ').append(render(text, true));
}
return;
}
buffer.append(render(tag.text(), true));
}
/**
* Renders the input using Asciidoctor.
*
* The source is first cleaned by stripping any trailing space after an
* end line (e.g., `"\n "`), which gets left behind by the Javadoc
* processor.
*
* @param input AsciiDoc source
* @return content rendered by Asciidoctor
*/
private String render(String input, boolean inline) {
if (input.trim().isEmpty()) {
return "";
}
options.setDocType(inline ? INLINE_DOCTYPE : null);
return asciidoctor.render(cleanJavadocInput(input), options);
}
protected static String cleanJavadocInput(String input) {
return input.trim()
.replaceAll("\n ", "\n") // Newline space to accommodate javadoc newlines.
.replaceAll("\\{at}", "@") // {at} is translated into @.
.replaceAll("\\{slash}", "/") // {slash} is translated into /.
.replaceAll("(?m)^( *)\\*\\\\/$", "$1*/") // Multi-line comment end tag is translated into */.
.replaceAll("\\{@literal (.*?)}", "$1"); // {@literal _} is translated into _ (standard javadoc).
}
}