mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
work in progress on rendering epydoc docstrings to HTML
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.jetbrains.python.documentation;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.xml.util.XmlTagUtilBase;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
@@ -11,6 +12,13 @@ public class EpydocString extends StructuredDocString {
|
||||
super(docstringText, "@");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
final String html = inlineMarkupToHTML(myDescription);
|
||||
assert html != null;
|
||||
return html;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getReturnType() {
|
||||
@@ -46,39 +54,101 @@ public class EpydocString extends StructuredDocString {
|
||||
@Nullable
|
||||
private static String convertInlineMarkup(String s, boolean toHTML) {
|
||||
if (s == null) return null;
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
appendWithMarkup(s, resultBuilder, toHTML);
|
||||
return resultBuilder.toString();
|
||||
MarkupConverter converter = toHTML ? new HTMLConverter() : new MarkupConverter();
|
||||
converter.appendWithMarkup(s);
|
||||
return converter.result();
|
||||
}
|
||||
|
||||
private static void appendWithMarkup(String s, StringBuilder resultBuilder, boolean toHTML) {
|
||||
int pos = 0;
|
||||
while(true) {
|
||||
int bracePos = s.indexOf('{', pos);
|
||||
if (bracePos < 1) break;
|
||||
char prevChar = s.charAt(bracePos-1);
|
||||
if (prevChar >= 'A' && prevChar <= 'Z') {
|
||||
resultBuilder.append(s.substring(pos, bracePos - 1));
|
||||
int rbracePos = findMatchingEndBrace(s, bracePos);
|
||||
if (rbracePos < 0) {
|
||||
pos = bracePos + 1;
|
||||
break;
|
||||
}
|
||||
final String inlineMarkupContent = s.substring(bracePos + 1, rbracePos);
|
||||
if (toHTML) {
|
||||
appendInlineMarkup(resultBuilder, prevChar, inlineMarkupContent);
|
||||
private static class MarkupConverter {
|
||||
protected final StringBuilder myResult = new StringBuilder();
|
||||
|
||||
public void appendWithMarkup(String s) {
|
||||
int pos = 0;
|
||||
while(true) {
|
||||
int bracePos = s.indexOf('{', pos);
|
||||
if (bracePos < 1) break;
|
||||
char prevChar = s.charAt(bracePos-1);
|
||||
if (prevChar >= 'A' && prevChar <= 'Z') {
|
||||
appendText(s.substring(pos, bracePos - 1));
|
||||
int rbracePos = findMatchingEndBrace(s, bracePos);
|
||||
if (rbracePos < 0) {
|
||||
pos = bracePos + 1;
|
||||
break;
|
||||
}
|
||||
final String inlineMarkupContent = s.substring(bracePos + 1, rbracePos);
|
||||
appendMarkup(prevChar, inlineMarkupContent);
|
||||
pos = rbracePos + 1;
|
||||
}
|
||||
else {
|
||||
resultBuilder.append(inlineMarkupContent);
|
||||
appendText(s.substring(pos, bracePos + 1));
|
||||
pos = bracePos+1;
|
||||
}
|
||||
pos = rbracePos + 1;
|
||||
}
|
||||
else {
|
||||
resultBuilder.append(StringUtil.escapeXml(joinLines(s.substring(pos, bracePos + 1), true)));
|
||||
pos = bracePos+1;
|
||||
appendText(s.substring(pos));
|
||||
}
|
||||
|
||||
protected void appendText(String text) {
|
||||
myResult.append(text);
|
||||
}
|
||||
|
||||
protected void appendMarkup(char markupChar, String markupContent) {
|
||||
myResult.append(markupContent);
|
||||
}
|
||||
|
||||
public String result() {
|
||||
return myResult.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class HTMLConverter extends MarkupConverter {
|
||||
@Override
|
||||
protected void appendText(String text) {
|
||||
myResult.append(joinLines(XmlTagUtilBase.escapeString(text, false), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendMarkup(char markupChar, String markupContent) {
|
||||
if (markupChar == 'U') {
|
||||
appendLink(markupContent);
|
||||
return;
|
||||
}
|
||||
switch (markupChar) {
|
||||
case 'I':
|
||||
appendTagPair(markupContent, "i");
|
||||
break;
|
||||
case 'B':
|
||||
appendTagPair(markupContent, "b");
|
||||
break;
|
||||
case 'C':
|
||||
appendTagPair(markupContent, "code");
|
||||
break;
|
||||
default:
|
||||
myResult.append(StringUtil.escapeXml(markupContent));
|
||||
break;
|
||||
}
|
||||
}
|
||||
resultBuilder.append(StringUtil.escapeXml(joinLines(s.substring(pos), true)));
|
||||
|
||||
private void appendTagPair(String markupContent, final String tagName) {
|
||||
myResult.append("<").append(tagName).append(">");
|
||||
appendWithMarkup(markupContent);
|
||||
myResult.append("</").append(tagName).append(">");
|
||||
}
|
||||
|
||||
private void appendLink(String markupContent) {
|
||||
String linkText = StringUtil.escapeXml(markupContent);
|
||||
String linkUrl = linkText;
|
||||
int pos = markupContent.indexOf('<');
|
||||
if (pos >= 0 && markupContent.endsWith(">")) {
|
||||
linkText = StringUtil.escapeXml(markupContent.substring(0, pos).trim());
|
||||
linkUrl = joinLines(StringUtil.escapeXml(markupContent.substring(pos + 1, markupContent.length() - 1)), false);
|
||||
}
|
||||
myResult.append("<a href=\"");
|
||||
if (!linkUrl.matches("[a-z]+:.+")) {
|
||||
myResult.append("http://");
|
||||
}
|
||||
myResult.append(linkUrl).append("\">").append(linkText).append("</a>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int findMatchingEndBrace(String s, int bracePos) {
|
||||
@@ -94,57 +164,23 @@ public class EpydocString extends StructuredDocString {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void appendInlineMarkup(StringBuilder resultBuilder, char markupChar, String markupContent) {
|
||||
if (markupChar == 'U') {
|
||||
appendLink(resultBuilder, markupContent);
|
||||
return;
|
||||
}
|
||||
switch (markupChar) {
|
||||
case 'I':
|
||||
appendTagPair(resultBuilder, markupContent, "i");
|
||||
break;
|
||||
case 'B':
|
||||
appendTagPair(resultBuilder, markupContent, "b");
|
||||
break;
|
||||
case 'C':
|
||||
appendTagPair(resultBuilder, markupContent, "pre");
|
||||
break;
|
||||
default:
|
||||
resultBuilder.append(StringUtil.escapeXml(markupContent));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendTagPair(StringBuilder resultBuilder, String markupContent, final String tagName) {
|
||||
resultBuilder.append("<").append(tagName).append(">");
|
||||
appendWithMarkup(markupContent, resultBuilder, true);
|
||||
resultBuilder.append("</").append(tagName).append(">");
|
||||
}
|
||||
|
||||
private static void appendLink(StringBuilder resultBuilder, String markupContent) {
|
||||
String linkText = StringUtil.escapeXml(markupContent);
|
||||
String linkUrl = linkText;
|
||||
int pos = markupContent.indexOf('<');
|
||||
if (pos >= 0 && markupContent.endsWith(">")) {
|
||||
linkText = StringUtil.escapeXml(markupContent.substring(0, pos).trim());
|
||||
linkUrl = StringUtil.escapeXml(joinLines(markupContent.substring(pos + 1, markupContent.length() - 1), false));
|
||||
}
|
||||
resultBuilder.append("<a href=\"");
|
||||
if (!linkUrl.matches("[a-z]+:.+")) {
|
||||
resultBuilder.append("http://");
|
||||
}
|
||||
resultBuilder.append(linkUrl).append("\">").append(linkText).append("</a>");
|
||||
}
|
||||
|
||||
private static String joinLines(String s, boolean addSpace) {
|
||||
while(true) {
|
||||
int lineBreakStart = s.indexOf('\n');
|
||||
if (lineBreakStart < 0) break;
|
||||
int lineBreakEnd = lineBreakStart+1;
|
||||
while(lineBreakEnd < s.length() && s.charAt(lineBreakEnd) == ' ') {
|
||||
int blankLines = 0;
|
||||
while(lineBreakEnd < s.length() && (s.charAt(lineBreakEnd) == ' ' || s.charAt(lineBreakEnd) == '\n')) {
|
||||
if (s.charAt(lineBreakEnd) == '\n') blankLines++;
|
||||
lineBreakEnd++;
|
||||
}
|
||||
s = s.substring(0, lineBreakStart) + (addSpace ? " " : "") + s.substring(lineBreakEnd);
|
||||
if (addSpace) {
|
||||
String separator = blankLines > 0 ? "<p>" : " ";
|
||||
s = s.substring(0, lineBreakStart) + separator + s.substring(lineBreakEnd);
|
||||
}
|
||||
else {
|
||||
s = s.substring(0, lineBreakStart) + s.substring(lineBreakEnd);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ import com.intellij.util.xmlb.XmlSerializerUtil;
|
||||
public class PyDocumentationSettings implements PersistentStateComponent<PyDocumentationSettings> {
|
||||
public String myDocStringFormat = DocStringFormat.PLAIN;
|
||||
|
||||
public boolean isEpydocFormat() {
|
||||
return DocStringFormat.EPYDOC.equals(myDocStringFormat);
|
||||
}
|
||||
|
||||
public static PyDocumentationSettings getInstance(Project project) {
|
||||
return ServiceManager.getService(project, PyDocumentationSettings.class);
|
||||
}
|
||||
|
||||
@@ -45,10 +45,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -181,8 +178,14 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider impl
|
||||
return cat;
|
||||
}
|
||||
|
||||
private static @NotNull ChainIterable<String> combUpDocString(Project project, @NotNull String docstring) {
|
||||
ChainIterable<String> cat = new ChainIterable<String>();
|
||||
private static @NotNull List<String> combUpDocString(Project project, @NotNull String docstring) {
|
||||
PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(project);
|
||||
List<String> result = new ArrayList<String>();
|
||||
if (documentationSettings.isEpydocFormat()) {
|
||||
final EpydocString epydocString = new EpydocString(docstring);
|
||||
result.add(formatStructuredDocString(epydocString));
|
||||
return result;
|
||||
}
|
||||
// detect common indentation
|
||||
String[] lines = LineTokenizer.tokenize(docstring, false);
|
||||
Pattern spaces_pat = Pattern.compile("^\\s+");
|
||||
@@ -217,7 +220,7 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider impl
|
||||
for (String line : lines) {
|
||||
if (is_first && spaces_pat.matcher(line).matches()) continue; // ignore all initial whitespace
|
||||
if (is_first) is_first = false;
|
||||
else cat.add(BR);
|
||||
else result.add(BR);
|
||||
int leadingTabs = 0;
|
||||
while (leadingTabs < line.length() && line.charAt(leadingTabs) == '\t') {
|
||||
leadingTabs++;
|
||||
@@ -225,9 +228,22 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider impl
|
||||
if (leadingTabs > 0) {
|
||||
line = StringUtil.repeatSymbol(' ', tabSize * leadingTabs) + line.substring(leadingTabs);
|
||||
}
|
||||
cat.add(combUp(line));
|
||||
result.add(combUp(line));
|
||||
}
|
||||
return cat;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String formatStructuredDocString(StructuredDocString docString) {
|
||||
StringBuilder result = new StringBuilder(docString.getDescription());
|
||||
final List<String> parameters = docString.getParameters();
|
||||
if (parameters.size() > 0) {
|
||||
result.append("<br><b>Parameters:</b><br>");
|
||||
for (String parameter : parameters) {
|
||||
result.append("<b>").append(parameter).append("</b>: ").append(docString.getParamDescription(parameter)).append("<br>");
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
|
||||
}
|
||||
|
||||
// provides ctrl+Q doc
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.jetbrains.python.documentation;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.intellij.openapi.util.text.LineTokenizer;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public abstract class StructuredDocString {
|
||||
protected final String myDescription;
|
||||
protected final List<String> myParameters = Lists.newArrayList();
|
||||
protected final Map<String, String> mySimpleTagValues = Maps.newHashMap();
|
||||
protected final Map<String, Map<String, String>> myArgTagValues = Maps.newHashMap();
|
||||
|
||||
@@ -24,13 +28,22 @@ public abstract class StructuredDocString {
|
||||
protected StructuredDocString(String docstringText, String tagPrefix) {
|
||||
final String[] lines = LineTokenizer.tokenize(docstringText, false);
|
||||
int i = 0;
|
||||
StringBuilder descBuilder = new StringBuilder();
|
||||
while (i < lines.length) {
|
||||
String line = lines[i].trim();
|
||||
if (line.startsWith(tagPrefix)) {
|
||||
i = parseTag(lines, i, tagPrefix);
|
||||
}
|
||||
else {
|
||||
descBuilder.append(line).append("\n");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
myDescription = descBuilder.toString();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return myDescription;
|
||||
}
|
||||
|
||||
private int parseTag(String[] lines, int index, String tagPrefix) {
|
||||
@@ -57,6 +70,9 @@ public abstract class StructuredDocString {
|
||||
myArgTagValues.put(tagName, argValues);
|
||||
}
|
||||
argValues.put(arg, value);
|
||||
if (tagName.equals("param")) {
|
||||
myParameters.add(arg);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
@@ -72,6 +88,10 @@ public abstract class StructuredDocString {
|
||||
return argValues == null ? null : argValues.get(argName);
|
||||
}
|
||||
|
||||
public List<String> getParameters() {
|
||||
return myParameters;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract String getReturnType();
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import com.jetbrains.python.documentation.EpydocString;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class EpydocStringTest extends TestCase {
|
||||
public class EpydocStringTest extends UsefulTestCase {
|
||||
public void testTagValue() {
|
||||
EpydocString docString = new EpydocString("@rtype: C{str}");
|
||||
assertEquals("C{str}", docString.getTagValue("rtype"));
|
||||
@@ -50,6 +52,9 @@ public class EpydocStringTest extends TestCase {
|
||||
" @param *args: arguments passed to function\n" +
|
||||
" @param **kwargs: keyword arguments passed to C{function}\n" +
|
||||
" \"\"\"");
|
||||
|
||||
final List<String> params = docString.getParameters();
|
||||
assertOrderedEquals(params, "euid", "egid", "function", "*args", "**kwargs");
|
||||
assertEquals("effective UID used to call the function.", docString.getParamDescription("euid"));
|
||||
assertEquals("effective GID used to call the function.", docString.getParamDescription("egid"));
|
||||
assertEquals("arguments passed to function", docString.getParamDescription("args"));
|
||||
@@ -60,7 +65,7 @@ public class EpydocStringTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testCodeToHTML() {
|
||||
assertEquals("<pre>my_dict={1:2, 3:4}</pre>", EpydocString.inlineMarkupToHTML("C{my_dict={1:2, 3:4}}"));
|
||||
assertEquals("<code>my_dict={1:2, 3:4}</code>", EpydocString.inlineMarkupToHTML("C{my_dict={1:2, 3:4}}"));
|
||||
}
|
||||
|
||||
public void testUrlToHTML() {
|
||||
@@ -77,4 +82,8 @@ public class EpydocStringTest extends TestCase {
|
||||
" it may span} multiple lines."));
|
||||
|
||||
}
|
||||
|
||||
public void testParagraph() {
|
||||
assertEquals("foo<p>bar", EpydocString.inlineMarkupToHTML("foo\n\nbar"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user