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 } 046 047 private static OptionsBuilder defaultOptions() { 048 return OptionsBuilder.options() 049 .safe(SafeMode.SAFE) 050 .backend("html5"); 051 } 052 053 protected static final String INLINE_DOCTYPE = "inline"; 054 055 private final Asciidoctor asciidoctor; 056 private final Optional<OutputTemplates> templates; 057 private final Options options; 058 059 public AsciidoctorRenderer(DocletOptions docletOptions, DocErrorReporter errorReporter) { 060 this(docletOptions, errorReporter, OutputTemplates.create(errorReporter), create(docletOptions.gemPath())); 061 } 062 063 /** 064 * Constructor used directly for testing purposes only. 065 */ 066 protected AsciidoctorRenderer(DocletOptions docletOptions, DocErrorReporter errorReporter, Optional<OutputTemplates> templates, Asciidoctor asciidoctor) { 067 this.asciidoctor = asciidoctor; 068 this.templates = templates; 069 this.options = buildOptions(docletOptions, errorReporter); 070 } 071 072 private Options buildOptions(DocletOptions docletOptions, DocErrorReporter errorReporter) { 073 OptionsBuilder opts = defaultOptions(); 074 if (docletOptions.baseDir().isPresent()) { 075 opts.baseDir(docletOptions.baseDir().get()); 076 } 077 if (templates.isPresent()) { 078 opts.templateDir(templates.get().templateDir()); 079 } 080 opts.attributes(buildAttributes(docletOptions, errorReporter)); 081 if (docletOptions.requires().size() > 0) { 082 for (String require : docletOptions.requires()) { 083 asciidoctor.rubyExtensionRegistry().requireLibrary(require); 084 } 085 } 086 return opts.get(); 087 } 088 089 private Attributes buildAttributes(DocletOptions docletOptions, DocErrorReporter errorReporter) { 090 return defaultAttributes() 091 .attributes(new AttributesLoader(asciidoctor, docletOptions, errorReporter).load()) 092 .get(); 093 } 094 095 /** 096 * Renders a generic document (class, field, method, etc) 097 * 098 * @param doc input 099 */ 100 @Override 101 public void renderDoc(Doc doc) { 102 // hide text that looks like tags (such as annotations in source code) from Javadoc 103 doc.setRawCommentText(doc.getRawCommentText().replaceAll("@([A-Z])", "{@literal @}$1")); 104 105 StringBuilder buffer = new StringBuilder(); 106 buffer.append(render(doc.commentText(), false)); 107 buffer.append('\n'); 108 for (Tag tag : doc.tags()) { 109 renderTag(tag, buffer); 110 buffer.append('\n'); 111 } 112 doc.setRawCommentText(buffer.toString()); 113 } 114 115 public void cleanup() { 116 if (templates.isPresent()) { 117 templates.get().delete(); 118 } 119 } 120 121 /** 122 * Renders a document tag in the standard way. 123 * 124 * @param tag input 125 * @param buffer output buffer 126 */ 127 private void renderTag(Tag tag, StringBuilder buffer) { 128 buffer.append(tag.name()).append(' '); 129 // Special handling for @param <T> tags 130 // See http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#@param 131 if ((tag instanceof ParamTag) && ((ParamTag) tag).isTypeParameter()) { 132 ParamTag paramTag = (ParamTag) tag; 133 buffer.append("<" + paramTag.parameterName() + ">"); 134 String text = paramTag.parameterComment(); 135 if (text.length() > 0) { 136 buffer.append(' ').append(render(text, true)); 137 } 138 return; 139 } 140 buffer.append(render(tag.text(), true)); 141 } 142 143 /** 144 * Renders the input using Asciidoctor. 145 * 146 * The source is first cleaned by stripping any trailing space after an 147 * end line (e.g., `"\n "`), which gets left behind by the Javadoc 148 * processor. 149 * 150 * @param input AsciiDoc source 151 * @return content rendered by Asciidoctor 152 */ 153 private String render(String input, boolean inline) { 154 if (input.trim().isEmpty()) { 155 return ""; 156 } 157 options.setDocType(inline ? INLINE_DOCTYPE : null); 158 return asciidoctor.render(cleanJavadocInput(input), options); 159 } 160 161 protected static String cleanJavadocInput(String input) { 162 return input.trim() 163 .replaceAll("\n ", "\n") // Newline space to accommodate javadoc newlines. 164 .replaceAll("\\{at}", "@") // {at} is translated into @. 165 .replaceAll("\\{slash}", "/") // {slash} is translated into /. 166 .replaceAll("(?m)^( *)\\*\\\\/$", "$1*/") // Multi-line comment end tag is translated into */. 167 .replaceAll("\\{@literal (.*?)}", "$1"); // {@literal _} is translated into _ (standard javadoc). 168 } 169}