google-nomulus/java/google/registry/documentation/MarkdownDocumentationFormatter.java
shicong 387042bf3a Enable flow documentation in external build
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=241934689
2019-04-05 11:49:49 -04:00

182 lines
6.2 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.documentation;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
import google.registry.documentation.FlowDocumentation.ErrorCase;
import java.util.ArrayList;
import java.util.List;
/**
* Formatter that converts flow documentation into Markdown.
*/
public final class MarkdownDocumentationFormatter {
/** Header for flow documentation HTML output. */
private static final String MARKDOWN_HEADER = "# Nomulus EPP Command API Documentation\n\n";
/** Pattern that naively matches HTML tags and entity references. */
private static final Pattern HTML_TAG_AND_ENTITY_PATTERN = Pattern.compile("<[^>]*>|&[^;]*;");
/** 8 character indentation. */
private static final String INDENT8 = Strings.repeat(" ", 8);
/** Max linewidth for our markdown docs. */
private static final int LINE_WIDTH = 80;
/**
* Returns the string with all HTML tags stripped. Also, removes a single space after any
* newlines that have one (we get a single space indent for all lines but the first because of
* the way that javadocs are written in comments).
*/
@VisibleForTesting
static String fixHtml(String value) {
Matcher matcher = HTML_TAG_AND_ENTITY_PATTERN.matcher(value);
int pos = 0;
StringBuilder result = new StringBuilder();
while (matcher.find(pos)) {
result.append(value, pos, matcher.start());
switch (matcher.group(0)) {
case "<p>":
// <p> is simply removed.
break;
case "&amp;":
result.append("&");
break;
case "&lt;":
result.append("<");
break;
case "&gt;":
result.append(">");
break;
case "&squot;":
result.append("'");
break;
case "&quot;":
result.append("\"");
break;
default:
throw new IllegalArgumentException("Unrecognized HTML sequence: " + matcher.group(0));
}
pos = matcher.end();
}
// Add the string after the last HTML sequence.
result.append(value.substring(pos));
return result.toString().replace("\n ", "\n");
}
/**
* Formats a list of words into a paragraph with less than maxWidth characters per line.
*/
@VisibleForTesting
static String formatParagraph(List<String> words, int maxWidth) {
int lineLength = 0;
StringBuilder output = new StringBuilder();
for (String word : words) {
// This check ensures that 1) don't add a space before the word and 2) always have at least
// one word per line, so that we don't mishandle a very long word at the end of a line by
// adding a blank line before the word.
if (lineLength > 0) {
// Do we have enough room for another word?
if (lineLength + 1 + word.length() > maxWidth) {
// No. End the line.
output.append('\n');
lineLength = 0;
} else {
// Yes: Insert a space before the word.
output.append(' ');
++lineLength;
}
}
output.append(word);
lineLength += word.length();
}
output.append('\n');
return output.toString();
}
/**
* Returns 'value' with words reflowed to maxWidth characters.
*/
@VisibleForTesting
static String reflow(String text, int maxWidth) {
// A list of words that will be constructed into the list of words in a paragraph.
ArrayList<String> words = new ArrayList<>();
// Read through the lines, process a paragraph every time we get a blank line.
StringBuilder resultBuilder = new StringBuilder();
for (String line : Splitter.on('\n').trimResults().split(text)) {
// If we got a blank line, format our current paragraph and start fresh.
if (line.trim().isEmpty()) {
resultBuilder.append(formatParagraph(words, maxWidth));
resultBuilder.append('\n');
words.clear();
continue;
}
// Split the line into words and add them to the current paragraph.
words.addAll(Splitter.on(
CharMatcher.breakingWhitespace()).omitEmptyStrings().splitToList(line));
}
// Format the last paragraph, if any.
if (!words.isEmpty()) {
resultBuilder.append(formatParagraph(words, maxWidth));
}
return resultBuilder.toString();
}
/** Returns a string of HTML representing the provided flow documentation objects. */
public static String generateMarkdownOutput(Iterable<FlowDocumentation> flowDocs) {
StringBuilder output = new StringBuilder();
output.append(MARKDOWN_HEADER);
for (FlowDocumentation flowDoc : flowDocs) {
output.append(String.format("## %s\n\n", flowDoc.getName()));
output.append("### Description\n\n");
output.append(String.format("%s\n\n", reflow(fixHtml(flowDoc.getClassDocs()), LINE_WIDTH)));
output.append("### Errors\n\n");
for (Long code : flowDoc.getErrorsByCode().keySet()) {
output.append(String.format("* %d\n", code));
for (ErrorCase error : flowDoc.getErrorsByCode().get(code)) {
output.append(" * ");
String wrappedReason = reflow(fixHtml(error.getReason()), LINE_WIDTH - 8);
// Replace internal newlines with indentation and strip the final newline.
output.append(wrappedReason.trim().replace("\n", "\n" + INDENT8));
output.append('\n');
}
}
output.append('\n');
}
return output.toString();
}
private MarkdownDocumentationFormatter() {}
}