mirror of
https://github.com/google/nomulus.git
synced 2025-05-02 04:57:51 +02:00
182 lines
6.2 KiB
Java
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 "&":
|
|
result.append("&");
|
|
break;
|
|
case "<":
|
|
result.append("<");
|
|
break;
|
|
case ">":
|
|
result.append(">");
|
|
break;
|
|
case "&squot;":
|
|
result.append("'");
|
|
break;
|
|
case """:
|
|
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() {}
|
|
}
|