mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-9795 First version of parsing of section-based docstrings
This commit is contained in:
@@ -17,6 +17,7 @@ package com.jetbrains.python.toolbox;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -34,10 +35,26 @@ import java.util.regex.Pattern;
|
||||
public class Substring implements CharSequence {
|
||||
private static final Pattern RE_NL = Pattern.compile("(\\r?\\n)");
|
||||
|
||||
@NotNull
|
||||
public static Substring fromMatcherGroup(@NotNull String s, @NotNull Matcher matcher, int groupNumber) {
|
||||
if (matcher.groupCount() < groupNumber || matcher.end(groupNumber) > s.length()) {
|
||||
throw new IllegalArgumentException("Inconsistent matcher, group number and underlying string");
|
||||
}
|
||||
return new Substring(s, matcher.start(groupNumber), matcher.end(groupNumber));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Substring fromMatcherGroup(@NotNull Substring s, @NotNull Matcher matcher, int groupNumber) {
|
||||
if (matcher.groupCount() < groupNumber || matcher.end(groupNumber) > s.length()) {
|
||||
throw new IllegalArgumentException("Inconsistent matcher, group number and underlying string");
|
||||
}
|
||||
return new Substring(s.getSuperString(), s.myStartOffset + matcher.start(groupNumber), s.myStartOffset + matcher.end(groupNumber));
|
||||
}
|
||||
|
||||
@NotNull private final String myString;
|
||||
private final int myStartOffset;
|
||||
private final int myEndOffset;
|
||||
|
||||
|
||||
public Substring(@NotNull String s) {
|
||||
this(s, 0, s.length());
|
||||
}
|
||||
@@ -87,21 +104,33 @@ public class Substring implements CharSequence {
|
||||
|
||||
@NotNull
|
||||
public List<Substring> split(@NotNull String regex) {
|
||||
return split(Pattern.compile(regex));
|
||||
return split(regex, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Substring> split(@NotNull String regex, int maxSplits) {
|
||||
return split(Pattern.compile(regex), maxSplits);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Substring> split(@NotNull Pattern pattern) {
|
||||
return split(pattern, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Substring> split(@NotNull Pattern pattern, int maxSplits) {
|
||||
final List<Substring> result = new ArrayList<Substring>();
|
||||
final Matcher m = pattern.matcher(myString);
|
||||
int start = myStartOffset;
|
||||
int end = myEndOffset;
|
||||
int splitCount = 0;
|
||||
if (m.find(start)) {
|
||||
do {
|
||||
splitCount++;
|
||||
end = m.start();
|
||||
result.add(createAnotherSubstring(start, Math.min(end, myEndOffset)));
|
||||
start = m.end();
|
||||
} while (end < myEndOffset && m.find());
|
||||
} while (end < myEndOffset && m.find() && splitCount < maxSplits);
|
||||
if (start < myEndOffset) {
|
||||
result.add(createAnotherSubstring(start, myEndOffset));
|
||||
}
|
||||
@@ -190,4 +219,26 @@ public class Substring implements CharSequence {
|
||||
private Substring createAnotherSubstring(int start, int end) {
|
||||
return new Substring(myString, start, end);
|
||||
}
|
||||
|
||||
public int getStartOffset() {
|
||||
return myStartOffset;
|
||||
}
|
||||
|
||||
public int getEndOffset() {
|
||||
return myEndOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* If both substrings share the same origin, returns new substring that includes both of them. Otherwise return {@code null}.
|
||||
*
|
||||
* @param other substring to concat with
|
||||
* @return new substring as described
|
||||
*/
|
||||
@Nullable
|
||||
public Substring getSmallestInclusiveSubstring(@NotNull Substring other) {
|
||||
if (myString.equals(other.myString)) {
|
||||
return new Substring(myString, Math.min(myStartOffset, other.myStartOffset), Math.max(myEndOffset, other.myEndOffset));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ public class DocStringFormat {
|
||||
public static final String EPYTEXT = "Epytext";
|
||||
public static final String REST = "reStructuredText";
|
||||
public static final String NUMPY = "NumPy";
|
||||
public static final String GOOGLE = "Google";
|
||||
|
||||
public static final List<String> ALL = ImmutableList.of(PLAIN, EPYTEXT, REST, NUMPY);
|
||||
public static final List<String> ALL = ImmutableList.of(PLAIN, EPYTEXT, REST, NUMPY, GOOGLE);
|
||||
|
||||
private DocStringFormat() {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.Pair;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.toolbox.Substring;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class GoogleCodeStyleDocString extends SectionBasedDocString {
|
||||
private static final Pattern SECTION_HEADER_RE = Pattern.compile("^\\s*(.+?):\\s*$");
|
||||
private static final Pattern FIELD_NAME_AND_TYPE_RE = Pattern.compile("\\s*(.+?)\\s*\\(\\s*(.+?)\\s*\\)\\s*");
|
||||
|
||||
public GoogleCodeStyleDocString(@NotNull String text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Pair<SectionField, Integer> parseFieldWithType(int lineNum, int sectionIndent) {
|
||||
return parseField(lineNum, sectionIndent, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Pair<SectionField, Integer> parseFieldWithNameAndOptionalType(int lineNum, int sectionIndent) {
|
||||
return parseField(lineNum, sectionIndent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h3>Example</h3>
|
||||
* <pre><code>
|
||||
* Attributes:
|
||||
* arg1 (int): field with name and optional type before description
|
||||
*
|
||||
* Raises:
|
||||
* RuntimeException: field with only type before description
|
||||
* </code></pre>
|
||||
*
|
||||
* @param typeBeforeColon according to Google Code Style there can be either type or name and type in parenthesis before colon
|
||||
*/
|
||||
@NotNull
|
||||
private Pair<SectionField, Integer> parseField(int lineNum, int sectionIndent, boolean typeBeforeColon) {
|
||||
Substring name = null, type = null, description;
|
||||
final List<Substring> parts = getLine(lineNum).split(":", 1);
|
||||
assert parts.size() <= 2;
|
||||
if (parts.size() < 2) {
|
||||
return Pair.create(null, lineNum);
|
||||
}
|
||||
final Substring textBeforeColon = parts.get(0);
|
||||
if (typeBeforeColon) {
|
||||
type = textBeforeColon.trim();
|
||||
}
|
||||
else {
|
||||
// TODO skip references in types like Napoleon does
|
||||
final Matcher matcher = FIELD_NAME_AND_TYPE_RE.matcher(textBeforeColon);
|
||||
if (matcher.matches()) {
|
||||
name = Substring.fromMatcherGroup(textBeforeColon, matcher, 1).trim();
|
||||
type = Substring.fromMatcherGroup(textBeforeColon, matcher, 2).trim();
|
||||
}
|
||||
else {
|
||||
name = textBeforeColon.trim();
|
||||
}
|
||||
}
|
||||
description = parts.get(1);
|
||||
final Pair<List<Substring>, Integer> pair = parseIndentedBlock(lineNum + 1, getLineIndent(lineNum), sectionIndent);
|
||||
final List<Substring> nestedBlock = pair.getFirst();
|
||||
if (!nestedBlock.isEmpty()) {
|
||||
//noinspection ConstantConditions
|
||||
description = description.getSmallestInclusiveSubstring(ContainerUtil.getLastItem(nestedBlock));
|
||||
}
|
||||
assert description != null;
|
||||
description = description.trim();
|
||||
return Pair.create(new SectionField(name, type, description), pair.getSecond());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Pair<String, Integer> parseSectionHeader(int lineNum) {
|
||||
final Matcher matcher = SECTION_HEADER_RE.matcher(getLine(lineNum));
|
||||
if (matcher.matches()) {
|
||||
return Pair.create(matcher.group(1).trim(), lineNum + 1);
|
||||
}
|
||||
return Pair.create(null, lineNum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.psi.StructuredDocString;
|
||||
import com.jetbrains.python.toolbox.Substring;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Common base class for docstring styles supported by Napoleon Sphinx extension.
|
||||
*
|
||||
* @author Mikhail Golubev
|
||||
* @see <a href="http://sphinxcontrib-napoleon.readthedocs.org/en/latest/index.html">Napoleon</a>
|
||||
*/
|
||||
public abstract class SectionBasedDocString implements StructuredDocString {
|
||||
private static final Map<String, String> SECTION_ALIASES =
|
||||
ImmutableMap.<String, String>builder()
|
||||
.put("arguments", "parameters")
|
||||
.put("args", "parameters")
|
||||
.put("parameters", "parameters")
|
||||
.put("keyword args", "keyword arguments")
|
||||
.put("keyword arguments", "keyword arguments")
|
||||
.put("other parameters", "other parameters")
|
||||
.put("attributes", "attributes")
|
||||
.put("methods", "methods")
|
||||
.put("note", "notes")
|
||||
.put("notes", "notes")
|
||||
.put("example", "examples")
|
||||
.put("examples", "examples")
|
||||
.put("return", "returns")
|
||||
.put("returns", "returns")
|
||||
.put("yield", "yields")
|
||||
.put("yields", "yields")
|
||||
.put("raises", "raises")
|
||||
.put("references", "references")
|
||||
.put("see also", "see also")
|
||||
.put("warning", "warnings")
|
||||
.put("warns", "warnings")
|
||||
.put("warnings", "warnings")
|
||||
.build();
|
||||
|
||||
private static ImmutableSet<String> SECTIONS_WITH_NAME_AND_TYPE = ImmutableSet.of("attributes", "methods",
|
||||
"parameters", "keyword arguments", "other parameters");
|
||||
private static ImmutableSet<String> SECTIONS_WITH_TYPE = ImmutableSet.of("returns", "raises", "yields");
|
||||
|
||||
protected final List<Substring> myLines;
|
||||
|
||||
private final List<Substring> myOtherContent = new ArrayList<Substring>();
|
||||
private final List<Section> mySections = new ArrayList<Section>();
|
||||
private final String mySummary;
|
||||
|
||||
protected SectionBasedDocString(@NotNull String text) {
|
||||
myLines = new Substring(text).splitLines();
|
||||
String summary = "";
|
||||
int lineNum = 0;
|
||||
while (lineNum < myLines.size()) {
|
||||
Pair<Section, Integer> result = parseSection(lineNum);
|
||||
if (result.getFirst() != null) {
|
||||
mySections.add(result.getFirst());
|
||||
lineNum = result.getSecond();
|
||||
}
|
||||
else {
|
||||
if (lineNum == 0 && isEmptyOrDoesNotExist(lineNum + 1)) {
|
||||
summary = getLine(0).trim().toString();
|
||||
}
|
||||
else {
|
||||
myOtherContent.add(getLine(lineNum));
|
||||
}
|
||||
lineNum++;
|
||||
}
|
||||
}
|
||||
mySummary = summary;
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Pair<Section, Integer> parseSection(int sectionStartLine) {
|
||||
final Pair<String, Integer> pair = parseSectionHeader(sectionStartLine);
|
||||
final String title = normalizeSectionTitle(pair.getFirst());
|
||||
if (title == null) {
|
||||
return Pair.create(null, sectionStartLine);
|
||||
}
|
||||
int lineNum = skipEmptyLines(pair.getSecond());
|
||||
final List<SectionField> fields = new ArrayList<SectionField>();
|
||||
final int sectionIndent = getLineIndent(sectionStartLine);
|
||||
while (!isSectionBreak(lineNum, sectionIndent)) {
|
||||
final Pair<SectionField, Integer> result = parseField(lineNum, title, sectionIndent);
|
||||
if (result.getFirst() == null) {
|
||||
break;
|
||||
}
|
||||
fields.add(result.getFirst());
|
||||
lineNum = skipEmptyLines(result.getSecond());
|
||||
}
|
||||
return Pair.create(new Section(title, fields), lineNum);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Pair<SectionField, Integer> parseField(int lineNum, @NotNull String sectionTitle, int sectionIndent) {
|
||||
if (SECTIONS_WITH_NAME_AND_TYPE.contains(sectionTitle)) {
|
||||
return parseFieldWithNameAndOptionalType(lineNum, sectionIndent);
|
||||
}
|
||||
if (SECTIONS_WITH_TYPE.contains(sectionTitle)) {
|
||||
return parseFieldWithType(lineNum, sectionIndent);
|
||||
}
|
||||
return parseGeneralField(lineNum, sectionIndent);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Pair<SectionField,Integer> parseGeneralField(int lineNum, int sectionIndent) {
|
||||
final Pair<List<Substring>, Integer> pair = parseIndentedBlock(lineNum, sectionIndent, sectionIndent);
|
||||
final Substring firstLine = ContainerUtil.getFirstItem(pair.getFirst());
|
||||
final Substring lastLine = ContainerUtil.getLastItem(pair.getFirst());
|
||||
if (firstLine != null && lastLine != null) {
|
||||
final Substring mergedSubstring = new Substring(firstLine.getSuperString(), firstLine.getStartOffset(), lastLine.getEndOffset());
|
||||
return Pair.create(new SectionField(null, null, mergedSubstring), pair.getSecond());
|
||||
}
|
||||
return Pair.create(null, pair.getSecond());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract Pair<SectionField,Integer> parseFieldWithType(int lineNum, int sectionIndent);
|
||||
|
||||
@NotNull
|
||||
protected abstract Pair<SectionField,Integer> parseFieldWithNameAndOptionalType(int lineNum, int sectionIndent);
|
||||
|
||||
@NotNull
|
||||
protected abstract Pair<String, Integer> parseSectionHeader(int lineNum);
|
||||
|
||||
protected int getLineIndent(int lineNum) {
|
||||
final Substring line = getLine(lineNum);
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
if (!Character.isSpaceChar(line.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int skipEmptyLines(int lineNum) {
|
||||
while (lineNum < myLines.size() && isEmpty(lineNum)) {
|
||||
lineNum++;
|
||||
}
|
||||
return lineNum;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String normalizeSectionTitle(@Nullable @NonNls String title) {
|
||||
return title == null ? null : SECTION_ALIASES.get(title.toLowerCase());
|
||||
}
|
||||
|
||||
private boolean isEmptyOrDoesNotExist(int lineNum) {
|
||||
return lineNum >= myLines.size() - 1 || isEmpty(lineNum);
|
||||
}
|
||||
|
||||
private boolean isEmpty(int lineNum) {
|
||||
return StringUtil.isEmptyOrSpaces(getLine(lineNum));
|
||||
}
|
||||
|
||||
private boolean isSectionStart(int lineNum) {
|
||||
final Pair<String, Integer> pair = parseSectionHeader(lineNum);
|
||||
return pair.getFirst() != null;
|
||||
}
|
||||
|
||||
private boolean isSectionBreak(int lineNum, int curSectionIndent) {
|
||||
return lineNum >= myLines.size() ||
|
||||
isSectionStart(lineNum) ||
|
||||
(!isEmpty(lineNum) && getLineIndent(lineNum) <= curSectionIndent);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Pair<List<Substring>, Integer> parseIndentedBlock(int lineNum, int blockIndent, int sectionIndent) {
|
||||
final List<Substring> result = new ArrayList<Substring>();
|
||||
while (!isSectionBreak(lineNum, sectionIndent) && (isEmpty(lineNum) || getLineIndent(lineNum) > blockIndent)) {
|
||||
result.add(getLine(lineNum));
|
||||
lineNum++;
|
||||
}
|
||||
|
||||
return Pair.create(result, lineNum);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Substring getLine(int indent) {
|
||||
return myLines.get(indent);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public List<Section> getSections() {
|
||||
return Collections.unmodifiableList(mySections);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String createParameterType(@NotNull String name, @NotNull String type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSummary() {
|
||||
return mySummary.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameters() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Substring> getParameterSubstrings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getParamType(@Nullable String paramName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Substring getParamTypeSubstring(@Nullable String paramName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getParamDescription(@Nullable String paramName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getKeywordArguments() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Substring> getKeywordArgumentSubstrings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getKeywordArgumentDescription(@Nullable String paramName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getReturnType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Substring getReturnTypeSubstring() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getReturnDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRaisedExceptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getRaisedExceptionDescription(@Nullable String exceptionName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAttributeDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Substring getTagValue(String... tagNames) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Substring getTagValue(String tagName, @NotNull String argName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Substring getTagValue(String[] tagNames, @NotNull String argName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Substring> getTagArguments(String... tagNames) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Substring getParamByNameAndKind(@NotNull String name, String kind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAdditionalTags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class Section {
|
||||
private final String myTitle;
|
||||
private final List<SectionField> myFields;
|
||||
|
||||
public Section(@NotNull String title, @NotNull List<SectionField> fields) {
|
||||
myTitle = title;
|
||||
myFields = new ArrayList<SectionField>(fields);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTitle() {
|
||||
return myTitle;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<SectionField> getFields() {
|
||||
return Collections.unmodifiableList(myFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Section section = (Section)o;
|
||||
|
||||
if (!myTitle.equals(section.myTitle)) return false;
|
||||
if (!myFields.equals(section.myFields)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myTitle.hashCode();
|
||||
result = 31 * result + myFields.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SectionField {
|
||||
private final Substring myName;
|
||||
private final Substring myType;
|
||||
private final Substring myDescription;
|
||||
|
||||
public SectionField(@Nullable Substring name, @Nullable Substring type, @Nullable Substring description) {
|
||||
myName = name;
|
||||
myType = type;
|
||||
myDescription = description;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Substring getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Substring getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Substring getDescription() {
|
||||
return myDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SectionField field = (SectionField)o;
|
||||
|
||||
if (myName != null ? !myName.equals(field.myName) : field.myName != null) return false;
|
||||
if (myType != null ? !myType.equals(field.myType) : field.myType != null) return false;
|
||||
if (myDescription != null ? !myDescription.equals(field.myDescription) : field.myDescription != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myName != null ? myName.hashCode() : 0;
|
||||
result = 31 * result + (myType != null ? myType.hashCode() : 0);
|
||||
result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// MethodsSection
|
||||
// AttributesSection
|
||||
// YieldsSection
|
||||
// RaisesSection
|
||||
// ReturnsSection
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
def func(x, y, *args, **kwargs):
|
||||
"""Summary
|
||||
|
||||
Parameters:
|
||||
x (int) : first parameter
|
||||
y: second parameter
|
||||
with longer description
|
||||
|
||||
Raises:
|
||||
Exception: if anything bad happens
|
||||
Returns:
|
||||
None: always
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.search.PsiElementProcessor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.documentation.GoogleCodeStyleDocString;
|
||||
import com.jetbrains.python.documentation.SectionBasedDocString.Section;
|
||||
import com.jetbrains.python.documentation.SectionBasedDocString.SectionField;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
|
||||
|
||||
public void testSimpleFunctionDocString() {
|
||||
myFixture.configureByFile(getTestName(true) + ".py");
|
||||
final String docStringText = findFirstDocString();
|
||||
|
||||
assertNotNull(docStringText);
|
||||
final GoogleCodeStyleDocString docString = new GoogleCodeStyleDocString(docStringText);
|
||||
assertEquals("Summary", docString.getSummary());
|
||||
final List<Section> sections = docString.getSections();
|
||||
assertSize(3, sections);
|
||||
|
||||
assertEquals("parameters", sections.get(0).getTitle());
|
||||
final List<SectionField> paramFields = sections.get(0).getFields();
|
||||
assertSize(2, paramFields);
|
||||
final SectionField firstParamField = paramFields.get(0);
|
||||
assertNotNull(firstParamField.getName());
|
||||
assertEquals("x", firstParamField.getName().toString());
|
||||
assertNotNull(firstParamField.getType());
|
||||
assertEquals("int", firstParamField.getType().toString());
|
||||
assertNotNull(firstParamField.getDescription());
|
||||
assertEquals("first parameter", firstParamField.getDescription().toString());
|
||||
|
||||
final SectionField secondParamField = paramFields.get(1);
|
||||
assertNotNull(secondParamField.getName());
|
||||
assertEquals("y", secondParamField.getName().toString());
|
||||
assertNull(secondParamField.getType());
|
||||
assertNotNull(secondParamField.getDescription());
|
||||
assertEquals("second parameter\n" +
|
||||
" with longer description", secondParamField.getDescription().toString());
|
||||
|
||||
assertEquals("raises", sections.get(1).getTitle());
|
||||
final List<SectionField> exceptionFields = sections.get(1).getFields();
|
||||
assertSize(1, exceptionFields);
|
||||
final SectionField firstExcField = exceptionFields.get(0);
|
||||
assertNull(firstExcField.getName());
|
||||
assertNotNull(firstExcField.getType());
|
||||
assertEquals("Exception", firstExcField.getType().toString());
|
||||
assertNotNull(firstExcField.getDescription());
|
||||
assertEquals("if anything bad happens", firstExcField.getDescription().toString());
|
||||
|
||||
assertEquals("returns", sections.get(2).getTitle());
|
||||
final List<SectionField> returnFields = sections.get(2).getFields();
|
||||
assertSize(1, returnFields);
|
||||
final SectionField firstReturnField = returnFields.get(0);
|
||||
assertNull(firstReturnField.getName());
|
||||
assertNotNull(firstReturnField.getType());
|
||||
assertEquals("None", firstReturnField.getType().toString());
|
||||
assertNotNull(firstReturnField.getDescription());
|
||||
assertEquals("always", firstReturnField.getDescription().toString());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String findFirstDocString() {
|
||||
final PsiElementProcessor.FindElement<PsiElement> processor = new PsiElementProcessor.FindElement<PsiElement>() {
|
||||
@Override
|
||||
public boolean execute(@NotNull PsiElement element) {
|
||||
if (element instanceof PyStringLiteralExpression && element.getFirstChild().getNode().getElementType() == PyTokenTypes.DOCSTRING) {
|
||||
return setFound(element);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
PsiTreeUtil.processElements(myFixture.getFile(), processor);
|
||||
if (!processor.isFound()) {
|
||||
return null;
|
||||
}
|
||||
final PsiElement foundElement = processor.getFoundElement();
|
||||
assertNotNull(foundElement);
|
||||
return ((PyStringLiteralExpression)foundElement).getStringValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return super.getTestDataPath() + "/docstrings";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user