diff --git a/python/src/com/jetbrains/python/documentation/NumpyDocString.java b/python/src/com/jetbrains/python/documentation/NumpyDocString.java new file mode 100644 index 000000000000..133238c966be --- /dev/null +++ b/python/src/com/jetbrains/python/documentation/NumpyDocString.java @@ -0,0 +1,46 @@ +/* + * 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 org.jetbrains.annotations.NotNull; + +/** + * @author Mikhail Golubev + */ +public class NumpyDocString extends SectionBasedDocString { + public NumpyDocString(@NotNull String text) { + super(text); + } + + @NotNull + @Override + protected Pair parseFieldWithType(int lineNum, int sectionIndent) { + return null; + } + + @NotNull + @Override + protected Pair parseFieldWithNameAndOptionalType(int lineNum, int sectionIndent) { + return null; + } + + @NotNull + @Override + protected Pair parseSectionHeader(int lineNum) { + return null; + } +} diff --git a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java index 1f8d0d0551f4..18727372eb18 100644 --- a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java +++ b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java @@ -38,6 +38,7 @@ import java.util.*; * @see Napoleon */ public abstract class SectionBasedDocString implements StructuredDocString { + /** * Frequently used section types */ @@ -79,31 +80,53 @@ public abstract class SectionBasedDocString implements StructuredDocString { protected final List myLines; - private final List myOtherContent = new ArrayList(); + private final Substring mySummary; private final List
mySections = new ArrayList
(); - private final String mySummary; + private final List myOtherContent = new ArrayList(); protected SectionBasedDocString(@NotNull String text) { myLines = new Substring(text).splitLines(); - String summary = ""; - int lineNum = 0; + List summary = Collections.emptyList(); + final int startLine = parseHeader(0); + int lineNum = skipEmptyLines(startLine); while (lineNum < myLines.size()) { - Pair result = parseSection(lineNum); - if (result.getFirst() != null) { - mySections.add(result.getFirst()); - lineNum = result.getSecond(); + final Pair parsedSection = parseSection(lineNum); + if (parsedSection.getFirst() != null) { + mySections.add(parsedSection.getFirst()); + lineNum = parsedSection.getSecond(); + } + else if (lineNum == startLine) { + final Pair, Integer> parsedSummary = parseSummary(lineNum); + summary = parsedSummary.getFirst(); + lineNum = parsedSummary.getSecond(); } else { - if (lineNum == 0 && isEmptyOrDoesNotExist(lineNum + 1)) { - summary = getLine(0).trim().toString(); - } - else { - myOtherContent.add(getLine(lineNum)); - } + myOtherContent.add(getLine(lineNum)); lineNum++; } + lineNum = skipEmptyLines(lineNum); } - mySummary = summary; + //noinspection ConstantConditions + mySummary = summary.isEmpty() ? null : mergeSubstrings(summary.get(0), summary.get(summary.size() - 1)).trim(); + } + + @NotNull + private Pair, Integer> parseSummary(int lineNum) { + final List result = new ArrayList(); + while (!isEmptyOrDoesNotExist(lineNum)) { + result.add(getLine(lineNum)); + lineNum++; + } + return Pair.create(result, lineNum); + } + + /** + * Used to parse e.g. optional function signature at the beginning of NumPy-style docstring + * + * @return first line from which to star parsing remaining sections + */ + protected int parseHeader(int startLine) { + return startLine; } @NotNull @@ -172,7 +195,7 @@ public abstract class SectionBasedDocString implements StructuredDocString { } private boolean isEmptyOrDoesNotExist(int lineNum) { - return lineNum >= myLines.size() - 1 || isEmpty(lineNum); + return lineNum < 0 || lineNum >= myLines.size() || isEmpty(lineNum); } private boolean isEmpty(int lineNum) { @@ -268,8 +291,13 @@ public abstract class SectionBasedDocString implements StructuredDocString { } @NotNull - protected Substring getLine(int indent) { - return myLines.get(indent); + protected Substring getLine(int lineNum) { + return myLines.get(lineNum); + } + + @Nullable + protected Substring getLineOrNull(int lineNum) { + return lineNum >= 0 && lineNum < myLines.size() ? myLines.get(lineNum) : null; } @VisibleForTesting @@ -285,7 +313,7 @@ public abstract class SectionBasedDocString implements StructuredDocString { @Override public String getSummary() { - return mySummary.toString(); + return mySummary != null ? mySummary.concatTrimmedLines("\n") : ""; } @Override @@ -407,6 +435,7 @@ public abstract class SectionBasedDocString implements StructuredDocString { return result; } + @Nullable private SectionField getFirstFieldForKeywordArgument(@NotNull final String name) { return ContainerUtil.find(getKeywordArgumentFields(), new Condition() { @Override diff --git a/python/testData/docstrings/multilineSummary.py b/python/testData/docstrings/multilineSummary.py new file mode 100644 index 000000000000..46a54a22ee7c --- /dev/null +++ b/python/testData/docstrings/multilineSummary.py @@ -0,0 +1,4 @@ +def func(): + """First line + Second line + Third line""" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyGoogleCodeStyleDocStringTest.java b/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java similarity index 85% rename from python/testSrc/com/jetbrains/python/PyGoogleCodeStyleDocStringTest.java rename to python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java index c02b3777d733..b6dc7aa0e43b 100644 --- a/python/testSrc/com/jetbrains/python/PyGoogleCodeStyleDocStringTest.java +++ b/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java @@ -19,6 +19,7 @@ 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.NumpyDocString; import com.jetbrains.python.documentation.SectionBasedDocString.Section; import com.jetbrains.python.documentation.SectionBasedDocString.SectionField; import com.jetbrains.python.fixtures.PyTestCase; @@ -31,10 +32,10 @@ import java.util.List; /** * @author Mikhail Golubev */ -public class PyGoogleCodeStyleDocStringTest extends PyTestCase { +public class PySectionBasedDocStringTest extends PyTestCase { public void testSimpleFunctionDocString() { - final GoogleCodeStyleDocString docString = findAndParseDocString(); + final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString(); assertEquals("Summary", docString.getSummary()); final List
sections = docString.getSections(); assertSize(3, sections); @@ -71,12 +72,22 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase { } @NotNull - private GoogleCodeStyleDocString findAndParseDocString() { + private String findAndParseDocString() { myFixture.configureByFile(getTestName(true) + ".py"); final String docStringText = findFirstDocString(); assertNotNull(docStringText); - return new GoogleCodeStyleDocString(docStringText); + return docStringText; + } + + @NotNull + private GoogleCodeStyleDocString findAndParseGoogleStyleDocString() { + return new GoogleCodeStyleDocString(findAndParseDocString()); + } + + @NotNull + private NumpyDocString findAndParseNumpyStyleDocString() { + return new NumpyDocString(findAndParseDocString()); } @Nullable @@ -100,7 +111,7 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase { } public void testSectionStartAfterQuotes() { - final GoogleCodeStyleDocString docString = findAndParseDocString(); + final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString(); assertEmpty(docString.getSummary()); assertSize(2, docString.getSections()); @@ -125,7 +136,7 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase { } public void testTypeReferences() { - final GoogleCodeStyleDocString docString = findAndParseDocString(); + final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString(); assertEmpty(docString.getSummary()); assertSize(2, docString.getSections()); @@ -145,7 +156,7 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase { } public void testNestedIndentation() { - final GoogleCodeStyleDocString docString = findAndParseDocString(); + final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString(); assertSize(1, docString.getSections()); final Section section1 = docString.getSections().get(0); assertEquals("parameters", section1.getTitle()); @@ -162,6 +173,13 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase { " assert func(42) is None", param1.getDescription()); } + public void testMultilineSummary() { + final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString(); + assertEquals("First line\n" + + "Second line\n" + + "Third line", docString.getSummary()); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/docstrings";