95bc1d2ed2d855e3fcc986492a022b67c438165c
[idea/community.git] / python / src / com / jetbrains / python / documentation / docstrings / PyStructuredDocstringFormatter.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.documentation.docstrings;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.configurations.GeneralCommandLine;
20 import com.intellij.execution.process.ProcessOutput;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.module.Module;
23 import com.intellij.openapi.module.ModuleManager;
24 import com.intellij.openapi.module.ModuleUtilCore;
25 import com.intellij.openapi.projectRoots.Sdk;
26 import com.intellij.openapi.vfs.CharsetToolkit;
27 import com.intellij.psi.PsiElement;
28 import com.jetbrains.python.PyBundle;
29 import com.jetbrains.python.PythonHelper;
30 import com.jetbrains.python.psi.PyIndentUtil;
31 import com.jetbrains.python.psi.PyStringLiteralExpression;
32 import com.jetbrains.python.psi.StructuredDocString;
33 import com.jetbrains.python.sdk.PySdkUtil;
34 import com.jetbrains.python.sdk.PythonSdkType;
35 import com.jetbrains.python.toolbox.Substring;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.io.File;
40 import java.nio.ByteBuffer;
41 import java.nio.charset.Charset;
42 import java.util.ArrayList;
43 import java.util.List;
44
45 /**
46  * @author yole
47  */
48 public class PyStructuredDocstringFormatter {
49   private static final Logger LOG = Logger.getInstance(PyStructuredDocstringFormatter.class);
50   private static final Charset DEFAULT_CHARSET = CharsetToolkit.UTF8_CHARSET;
51
52   private PyStructuredDocstringFormatter() {
53   }
54
55   /**
56    * @param docstring docstring text without string literal prefix, without quotes and already escaped. 
57    *                  Supposedly result of {@link PyStringLiteralExpression#getStringValue()}.
58    */
59   @Nullable
60   public static List<String> formatDocstring(@NotNull final PsiElement element, @NotNull final String docstring) {
61     Module module = ModuleUtilCore.findModuleForPsiElement(element);
62     if (module == null) {
63       final Module[] modules = ModuleManager.getInstance(element.getProject()).getModules();
64       if (modules.length == 0) return Lists.newArrayList();
65       module = modules[0];
66     }
67     if (module == null) return Lists.newArrayList();
68     final List<String> result = new ArrayList<>();
69
70     final String preparedDocstring = PyIndentUtil.removeCommonIndent(docstring, true).trim();
71
72     final DocStringFormat format = DocStringUtil.guessDocStringFormat(preparedDocstring, element);
73     if (format == DocStringFormat.PLAIN) {
74       return null;
75     }
76
77     final StructuredDocString structuredDocString = DocStringUtil.parseDocStringContent(format, preparedDocstring);
78
79     final String output = runExternalTool(module, format, preparedDocstring);
80     if (output != null) {
81       result.add(output);
82     }
83     else {
84       result.add(structuredDocString.getDescription());
85     }
86
87     // Information about parameters in Epytext-style docstrings are formatter on our side
88     if (format == DocStringFormat.EPYTEXT) {
89       result.add(formatStructuredDocString(structuredDocString));
90     }
91
92     return result;
93   }
94
95   @Nullable
96   private static String runExternalTool(@NotNull final Module module,
97                                         @NotNull final DocStringFormat format,
98                                         @NotNull final String docstring) {
99     final Sdk sdk;
100     final String missingInterpreterMessage;
101     if (format == DocStringFormat.EPYTEXT) {
102       sdk = PythonSdkType.findPython2Sdk(module);
103       missingInterpreterMessage = PyBundle.message("QDOC.epydoc.python2.sdk.not.found");
104     }
105     else {
106       sdk = PythonSdkType.findLocalCPython(module);
107       missingInterpreterMessage = PyBundle.message("QDOC.sdk.not.found");
108     }
109     if (sdk == null) {
110       LOG.warn("Python SDK for docstring formatter " + format +  " is not found");
111       return "<p color=\"red\">" + missingInterpreterMessage + "</p>";
112     }
113
114     final String sdkHome = sdk.getHomePath();
115     if (sdkHome == null) return null;
116
117     final ByteBuffer encoded = DEFAULT_CHARSET.encode(docstring);
118     final byte[] data = new byte[encoded.limit()];
119     encoded.get(data);
120
121     final ArrayList<String> arguments = Lists.newArrayList(format.getFormatterCommand());
122     final GeneralCommandLine commandLine = PythonHelper.DOCSTRING_FORMATTER.newCommandLine(sdk, arguments);
123     commandLine.setCharset(DEFAULT_CHARSET);
124
125     LOG.debug("Command for launching docstring formatter: " + commandLine.getCommandLineString());
126     
127     final ProcessOutput output = PySdkUtil.getProcessOutput(commandLine, new File(sdkHome).getParent(), null, 5000, data, false);
128     if (!output.checkSuccess(LOG)) {
129       LOG.info("Malformed docstring:\n" + docstring);
130       return null;
131     }
132     return output.getStdout();
133   }
134
135   private static String formatStructuredDocString(@NotNull final StructuredDocString docString) {
136     final StringBuilder result = new StringBuilder();
137
138     final String attributeDescription = docString.getAttributeDescription();
139     if (attributeDescription != null) {
140       result.append(attributeDescription);
141       final String attrType = docString.getParamType(null);
142       if (attrType != null) {
143         result.append(" <i>Type: ").append(attrType).append("</i>");
144       }
145     }
146
147     formatParameterDescriptions(docString, result, false);
148     formatParameterDescriptions(docString, result, true);
149
150     final String returnDescription = docString.getReturnDescription();
151     final String returnType = docString.getReturnType();
152     if (returnDescription != null || returnType != null) {
153       result.append("<br><b>Return value:</b><br>");
154       if (returnDescription != null) {
155         result.append(returnDescription);
156       }
157       if (returnType != null) {
158         result.append(" <i>Type: ").append(returnType).append("</i>");
159       }
160     }
161
162     final List<String> raisedException = docString.getRaisedExceptions();
163     if (raisedException.size() > 0) {
164       result.append("<br><b>Raises:</b><br>");
165       for (String s : raisedException) {
166         result.append("<b>").append(s).append("</b> - ").append(docString.getRaisedExceptionDescription(s)).append("<br>");
167       }
168     }
169
170     if (docString instanceof TagBasedDocString) {
171       final TagBasedDocString taggedDocString = (TagBasedDocString)docString;
172       final List<String> additionalTags = taggedDocString.getAdditionalTags();
173       if (!additionalTags.isEmpty()) {
174         result.append("<br/><br/><b>Additional:</b><br/>");
175         result.append("<table>");
176         for (String tagName : additionalTags) {
177           final List<Substring> args = taggedDocString.getTagArguments(tagName);
178           for (Substring arg : args) {
179             final String s = arg.toString();
180             result.append("<tr><td align=\"right\"><b>").append(tagName);
181             result.append(" ").append(s).append(":</b>");
182             result.append("</td><td>").append(taggedDocString.getTagValue(tagName, s)).append("</td></tr>");
183           }
184           result.append("</table>");
185         }
186       }
187     }
188     return result.toString();
189   }
190
191   private static void formatParameterDescriptions(@NotNull final StructuredDocString docString,
192                                                   @NotNull final StringBuilder result,
193                                                   boolean keyword) {
194     final List<String> parameters = keyword ? docString.getKeywordArguments() : docString.getParameters();
195     if (parameters.size() > 0) {
196       result.append("<br><b>").append(keyword ? "Keyword arguments:" : "Parameters").append("</b><br>");
197       for (String parameter : parameters) {
198         final String description = keyword ? docString.getKeywordArgumentDescription(parameter) : docString.getParamDescription(parameter);
199         result.append("<b>");
200         result.append(parameter);
201         result.append("</b>: ");
202         if (description != null) {
203           result.append(description);
204         }
205         final String paramType = docString.getParamType(parameter);
206         if (paramType != null) {
207           result.append(" <i>Type: ").append(paramType).append("</i>");
208         }
209         result.append("<br>");
210       }
211     }
212   }
213 }