blob: 3d0fc3e89d9eb74d018928839a62206da9299335 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 com.jetbrains.python.documentation;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.xml.util.XmlTagUtilBase;
import com.jetbrains.python.toolbox.Substring;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class EpydocString extends StructuredDocStringBase {
public static String[] RTYPE_TAGS = new String[] { "rtype", "returntype" };
public static String[] KEYWORD_ARGUMENT_TAGS = new String[] { "keyword", "kwarg", "kwparam" };
public static String[] ALL_TAGS = new String[] {
"@param", "@type", "@return", "@rtype", "@keyword", "@raise", "@ivar", "@cvar", "@var", "@group", "@sort", "@note", "@attention",
"@bug", "@warning", "@version", "@todo", "@deprecated", "@since", "@status", "@change", "@permission", "@requires",
"@precondition", "@postcondition", "@invariant", "@author", "@organization", "@copyright", "@license", "@contact", "@summary", "@see"
};
public static String[] ADDITIONAL = new String[] {
"group", "sort", "note", "attention",
"bug", "warning", "version", "todo", "deprecated", "since", "status", "change", "permission", "requires",
"precondition", "postcondition", "invariant", "author", "organization", "copyright", "license", "contact", "summary", "see"
};
/**
* Empty doc (for {@link #createParameterType(String, String)} probably)
*/
public EpydocString() {
this("");
}
public EpydocString(@NotNull String docstringText) {
super(docstringText, "@");
}
@Override
public String getDescription() {
final String html = inlineMarkupToHTML(myDescription);
assert html != null;
return html;
}
@Override
public List<String> getParameters() {
return toUniqueStrings(getParameterSubstrings());
}
@Override
public List<String> getKeywordArguments() {
return toUniqueStrings(getKeywordArgumentSubstrings());
}
@Override
@Nullable
public String getReturnType() {
return removeInlineMarkup(getReturnTypeSubstring());
}
@Override
public String getReturnDescription() {
return inlineMarkupToHTML(getTagValue(RETURN_TAGS));
}
@Override
@Nullable
public String getParamType(@Nullable String paramName) {
return removeInlineMarkup(getParamTypeSubstring(paramName));
}
@Override
@Nullable
public String getParamDescription(@Nullable String paramName) {
if (paramName == null) {
return null;
}
Substring value = getTagValue(PARAM_TAGS, paramName);
if (value == null) {
value = getTagValue(PARAM_TAGS, "*" + paramName);
}
if (value == null) {
value = getTagValue(PARAM_TAGS, "**" + paramName);
}
return inlineMarkupToHTML(value);
}
@Nullable
@Override
public String getKeywordArgumentDescription(@Nullable String paramName) {
if (paramName == null) {
return null;
}
return inlineMarkupToHTML(getTagValue(KEYWORD_ARGUMENT_TAGS, paramName));
}
@Override
public List<String> getRaisedExceptions() {
return toUniqueStrings(getTagArguments(RAISES_TAGS));
}
@Override
public String getRaisedExceptionDescription(@Nullable String exceptionName) {
if (exceptionName == null) {
return null;
}
return removeInlineMarkup(getTagValue(RAISES_TAGS, exceptionName));
}
@Override
public String getAttributeDescription() {
final Substring value = getTagValue(VARIABLE_TAGS);
return convertInlineMarkup(value != null ? value.toString() : null, true);
}
@Nullable
public static String removeInlineMarkup(@Nullable String s) {
return convertInlineMarkup(s, false);
}
@Nullable
private static String removeInlineMarkup(@Nullable Substring s) {
return convertInlineMarkup(s != null ? s.concatTrimmedLines(" ") : null, false);
}
@Nullable
private static String convertInlineMarkup(@Nullable String s, boolean toHTML) {
if (s == null) return null;
MarkupConverter converter = toHTML ? new HTMLConverter() : new MarkupConverter();
converter.appendWithMarkup(s);
return converter.result();
}
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 {
appendText(s.substring(pos, bracePos + 1));
pos = bracePos+1;
}
}
appendText(s.substring(pos));
}
protected void appendText(String text) {
myResult.append(text);
}
protected void appendMarkup(char markupChar, String markupContent) {
appendWithMarkup(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;
}
}
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) {
int braceCount = 1;
for(int pos=bracePos+1; pos < s.length(); pos++) {
char c = s.charAt(pos);
if (c == '{') braceCount++;
else if (c == '}') {
braceCount--;
if (braceCount == 0) return pos;
}
}
return -1;
}
private static String joinLines(String s, boolean addSpace) {
while(true) {
int lineBreakStart = s.indexOf('\n');
if (lineBreakStart < 0) break;
int lineBreakEnd = lineBreakStart+1;
int blankLines = 0;
while(lineBreakEnd < s.length() && (s.charAt(lineBreakEnd) == ' ' || s.charAt(lineBreakEnd) == '\n')) {
if (s.charAt(lineBreakEnd) == '\n') blankLines++;
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;
}
@Nullable
public static String inlineMarkupToHTML(@Nullable String s) {
return convertInlineMarkup(s, true);
}
@Nullable
private static String inlineMarkupToHTML(@Nullable Substring s) {
String text = "";
if (s != null) {
text = s.concatTrimmedLines(" ");
if (!text.endsWith(".")) text +=".";
}
return inlineMarkupToHTML(text);
}
public List<String> getAdditionalTags() {
List<String> list = new ArrayList<String>();
for (String tagName : ADDITIONAL) {
final Map<Substring, Substring> map = myArgTagValues.get(tagName);
if (map != null) {
list.add(tagName);
}
}
return list;
}
@Override
public List<Substring> getKeywordArgumentSubstrings() {
return getTagArguments(KEYWORD_ARGUMENT_TAGS);
}
@Override
public Substring getReturnTypeSubstring() {
return getTagValue(RTYPE_TAGS);
}
@Override
public Substring getParamTypeSubstring(@Nullable String paramName) {
return paramName == null ? getTagValue("type") : getTagValue("type", paramName);
}
}