Merge.
[idea/community.git] / python / src / com / jetbrains / python / documentation / 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;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.execution.process.ProcessOutput;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.module.ModuleManager;
23 import com.intellij.openapi.module.ModuleUtilCore;
24 import com.intellij.openapi.projectRoots.Sdk;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
27 import com.intellij.psi.PsiElement;
28 import com.jetbrains.python.HelperPackage;
29 import com.jetbrains.python.PythonHelper;
30 import com.jetbrains.python.psi.StructuredDocString;
31 import com.jetbrains.python.sdk.PySdkUtil;
32 import com.jetbrains.python.sdk.PythonEnvUtil;
33 import com.jetbrains.python.sdk.PythonSdkType;
34 import com.jetbrains.python.toolbox.Substring;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import java.io.File;
39 import java.nio.ByteBuffer;
40 import java.nio.charset.Charset;
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45
46 /**
47  * @author yole
48  */
49 public class PyStructuredDocstringFormatter {
50   private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.documentation.PyStructuredDocstringFormatter");
51
52   private PyStructuredDocstringFormatter() {
53   }
54
55   @Nullable
56   public static List<String> formatDocstring(@NotNull final PsiElement element, @NotNull final String docstring) {
57     Module module = ModuleUtilCore.findModuleForPsiElement(element);
58     if (module == null) {
59       final Module[] modules = ModuleManager.getInstance(element.getProject()).getModules();
60       if (modules.length == 0) return Lists.newArrayList();
61       module = modules[0];
62     }
63     if (module == null) return Lists.newArrayList();
64     final PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(module);
65     final List<String> result = new ArrayList<String>();
66
67     final String[] lines = PyDocumentationBuilder.removeCommonIndentation(docstring);
68     final String preparedDocstring = StringUtil.join(lines, "\n");
69
70     final HelperPackage formatter;
71     final StructuredDocStringBase structuredDocString;
72     if (documentationSettings.isEpydocFormat(element.getContainingFile()) ||
73         DocStringUtil.isEpydocDocString(preparedDocstring)) {
74       formatter = PythonHelper.EPYDOC_FORMATTER;
75       structuredDocString = new EpydocString(preparedDocstring);
76       result.add(formatStructuredDocString(structuredDocString));
77     }
78     else if (documentationSettings.isReSTFormat(element.getContainingFile()) ||
79              DocStringUtil.isSphinxDocString(preparedDocstring)) {
80       formatter = PythonHelper.REST_FORMATTER;
81       structuredDocString = new SphinxDocString(preparedDocstring);
82     }
83     else {
84       return null;
85     }
86
87     final String output = runExternalTool(module, formatter, docstring);
88     if (output != null)
89       result.add(0, output);
90     else
91       result.add(0, structuredDocString.getDescription());
92
93     return result;
94   }
95
96   @Nullable
97   private static String runExternalTool(@NotNull final Module module,
98                                         @NotNull final HelperPackage formatter,
99                                         @NotNull final String docstring) {
100     final Sdk sdk = PythonSdkType.findPython2Sdk(module);
101     if (sdk == null) return null;
102
103     final String sdkHome = sdk.getHomePath();
104     if (sdkHome == null) return null;
105
106     final Charset charset = EncodingProjectManager.getInstance(module.getProject()).getDefaultCharset();
107
108     final ByteBuffer encoded = charset.encode(docstring);
109     final byte[] data = new byte[encoded.limit()];
110     encoded.get(data);
111
112     final Map<String, String> env = new HashMap<String, String>();
113     PythonEnvUtil.setPythonDontWriteBytecode(env);
114
115     final ProcessOutput output = PySdkUtil.getProcessOutput(formatter.newCommandLine(sdkHome, Lists.<String>newArrayList()),
116                                                             new File(sdkHome).getParent(),
117                                                             env, 5000, data, true);
118     if (output.isTimeout()) {
119       LOG.info("timeout when calculating docstring");
120       return null;
121     }
122     else if (output.getExitCode() != 0) {
123       final String error = "error when calculating docstring: " + output.getStderr();
124       LOG.info(error);
125       return null;
126     }
127     return output.getStdout();
128   }
129
130   private static String formatStructuredDocString(@NotNull final StructuredDocString docString) {
131     final StringBuilder result = new StringBuilder();
132
133     final String attributeDescription = docString.getAttributeDescription();
134     if (attributeDescription != null) {
135       result.append(attributeDescription);
136       final String attrType = docString.getParamType(null);
137       if (attrType != null) {
138         result.append(" <i>Type: ").append(attrType).append("</i>");
139       }
140     }
141
142     formatParameterDescriptions(docString, result, false);
143     formatParameterDescriptions(docString, result, true);
144
145     final String returnDescription = docString.getReturnDescription();
146     final String returnType = docString.getReturnType();
147     if (returnDescription != null || returnType != null) {
148       result.append("<br><b>Return value:</b><br>");
149       if (returnDescription != null) {
150         result.append(returnDescription);
151       }
152       if (returnType != null) {
153         result.append(" <i>Type: ").append(returnType).append("</i>");
154       }
155     }
156
157     final List<String> raisedException = docString.getRaisedExceptions();
158     if (raisedException.size() > 0) {
159       result.append("<br><b>Raises:</b><br>");
160       for (String s : raisedException) {
161         result.append("<b>").append(s).append("</b> - ").append(docString.getRaisedExceptionDescription(s)).append("<br>");
162       }
163     }
164
165     final List<String> additionalTags = docString.getAdditionalTags();
166     if (!additionalTags.isEmpty()) {
167       result.append("<br/><br/><b>Additional:</b><br/>");
168       result.append("<table>");
169       for (String tagName : additionalTags) {
170         final List<Substring> args = docString.getTagArguments(tagName);
171         for (Substring arg : args) {
172           final String s = arg.toString();
173           result.append("<tr><td align=\"right\"><b>").append(tagName);
174           result.append(" ").append(s).append(":</b>");
175           result.append("</td><td>").append(docString.getTagValue(tagName, s)).append("</td></tr>");
176         }
177         result.append("</table>");
178       }
179     }
180     return result.toString();
181   }
182
183   private static void formatParameterDescriptions(@NotNull final StructuredDocString docString,
184                                                   @NotNull final StringBuilder result,
185                                                   boolean keyword) {
186     final List<String> parameters = keyword ? docString.getKeywordArguments() : docString.getParameters();
187     if (parameters.size() > 0) {
188       result.append("<br><b>").append(keyword ? "Keyword arguments:" : "Parameters").append("</b><br>");
189       for (String parameter : parameters) {
190         final String description = keyword ? docString.getKeywordArgumentDescription(parameter) : docString.getParamDescription(parameter);
191         result.append("<b>");
192         result.append(parameter);
193         result.append("</b>: ");
194         if (description != null) {
195           result.append(description);
196         }
197         final String paramType = docString.getParamType(parameter);
198         if (paramType != null) {
199           result.append(" <i>Type: ").append(paramType).append("</i>");
200         }
201         result.append("<br>");
202       }
203     }
204   }
205 }