001/** 002 * Copyright 2013-2015 John Ericksen 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.asciidoctor.asciidoclet; 017 018import com.google.common.base.Optional; 019import com.sun.javadoc.Doc; 020import com.sun.javadoc.DocErrorReporter; 021import com.sun.javadoc.ParamTag; 022import com.sun.javadoc.Tag; 023import org.asciidoctor.*; 024 025import static org.asciidoctor.Asciidoctor.Factory.create; 026 027/** 028 * Doclet renderer using and configuring Asciidoctor. 029 * 030 * @author John Ericksen 031 */ 032public class AsciidoctorRenderer implements DocletRenderer { 033 034 private static AttributesBuilder defaultAttributes() { 035 return AttributesBuilder.attributes() 036 .attribute("at", "@") 037 .attribute("slash", "/") 038 .attribute("icons", null) 039 .attribute("idprefix", "") 040 .attribute("idseparator", "-") 041 .attribute("javadoc", "") 042 .attribute("showtitle", true) 043 .attribute("source-highlighter", "coderay") 044 .attribute("coderay-css", "class") 045 .attribute("env-asciidoclet") 046 .attribute("env", "asciidoclet"); 047 } 048 049 private static OptionsBuilder defaultOptions() { 050 return OptionsBuilder.options() 051 .safe(SafeMode.SAFE) 052 .backend("html5"); 053 } 054 055 protected static final String INLINE_DOCTYPE = "inline"; 056 057 private final Asciidoctor asciidoctor; 058 private final Optional<OutputTemplates> templates; 059 private final Options options; 060 061 public AsciidoctorRenderer(DocletOptions docletOptions, DocErrorReporter errorReporter) { 062 this(docletOptions, errorReporter, OutputTemplates.create(errorReporter), create(docletOptions.gemPath())); 063 } 064 065 /** 066 * Constructor used directly for testing purposes only. 067 */ 068 protected AsciidoctorRenderer(DocletOptions docletOptions, DocErrorReporter errorReporter, Optional<OutputTemplates> templates, Asciidoctor asciidoctor) { 069 this.asciidoctor = asciidoctor; 070 this.templates = templates; 071 this.options = buildOptions(docletOptions, errorReporter); 072 } 073 074 private Options buildOptions(DocletOptions docletOptions, DocErrorReporter errorReporter) { 075 OptionsBuilder opts = defaultOptions(); 076 if (docletOptions.baseDir().isPresent()) { 077 opts.baseDir(docletOptions.baseDir().get()); 078 } 079 if (templates.isPresent()) { 080 opts.templateDir(templates.get().templateDir()); 081 } 082 opts.attributes(buildAttributes(docletOptions, errorReporter)); 083 if (docletOptions.requires().size() > 0) { 084 for (String require : docletOptions.requires()) { 085 asciidoctor.rubyExtensionRegistry().requireLibrary(require); 086 } 087 } 088 return opts.get(); 089 } 090 091 private Attributes buildAttributes(DocletOptions docletOptions, DocErrorReporter errorReporter) { 092 return defaultAttributes() 093 .attributes(new AttributesLoader(asciidoctor, docletOptions, errorReporter).load()) 094 .get(); 095 } 096 097 /** 098 * Renders a generic document (class, field, method, etc) 099 * 100 * @param doc input 101 */ 102 @Override 103 public void renderDoc(Doc doc) { 104 // hide text that looks like tags (such as annotations in source code) from Javadoc 105 doc.setRawCommentText(doc.getRawCommentText().replaceAll("@([A-Z])", "{@literal @}$1")); 106 107 StringBuilder buffer = new StringBuilder(); 108 buffer.append(render(doc.commentText(), false)); 109 buffer.append('\n'); 110 for (Tag tag : doc.tags()) { 111 renderTag(tag, buffer); 112 buffer.append('\n'); 113 } 114 doc.setRawCommentText(buffer.toString()); 115 } 116 117 public void cleanup() { 118 if (templates.isPresent()) { 119 templates.get().delete(); 120 } 121 } 122 123 /** 124 * Renders a document tag in the standard way. 125 * 126 * @param tag input 127 * @param buffer output buffer 128 */ 129 private void renderTag(Tag tag, StringBuilder buffer) { 130 buffer.append(tag.name()).append(' '); 131 // Special handling for @param <T> tags 132 // See http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#@param 133 if ((tag instanceof ParamTag) && ((ParamTag) tag).isTypeParameter()) { 134 ParamTag paramTag = (ParamTag) tag; 135 buffer.append("<" + paramTag.parameterName() + ">"); 136 String text = paramTag.parameterComment(); 137 if (text.length() > 0) { 138 buffer.append(' ').append(render(text, true)); 139 } 140 return; 141 } 142 buffer.append(render(tag.text(), true)); 143 } 144 145 /** 146 * Renders the input using Asciidoctor. 147 * 148 * The source is first cleaned by stripping any trailing space after an 149 * end line (e.g., `"\n "`), which gets left behind by the Javadoc 150 * processor. 151 * 152 * @param input AsciiDoc source 153 * @return content rendered by Asciidoctor 154 */ 155 private String render(String input, boolean inline) { 156 if (input.trim().isEmpty()) { 157 return ""; 158 } 159 options.setDocType(inline ? INLINE_DOCTYPE : null); 160 return asciidoctor.render(cleanJavadocInput(input), options); 161 } 162 163 protected static String cleanJavadocInput(String input) { 164 return input.trim() 165 .replaceAll("\n ", "\n") // Newline space to accommodate javadoc newlines. 166 .replaceAll("\\{at}", "@") // {at} is translated into @. 167 .replaceAll("\\{slash}", "/") // {slash} is translated into /. 168 .replaceAll("(?m)^( *)\\*\\\\/$", "$1*/") // Multi-line comment end tag is translated into */. 169 .replaceAll("\\{@literal (.*?)}", "$1"); // {@literal _} is translated into _ (standard javadoc). 170 } 171}