import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.startup.StartupActivity;
-import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.platform.DirectoryProjectConfigurator;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.FilenameIndex;
import com.jetbrains.python.PythonModuleTypeBase;
import com.jetbrains.python.documentation.PyDocumentationSettings;
import com.jetbrains.python.documentation.docstrings.DocStringFormat;
+import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.packaging.PyPackageUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.sdk.PythonSdkType;
* Detects test runner and docstring format
*
*/
-public class PyTestRunnerUpdater implements StartupActivity {
+public class PyIntegratedToolsProjectConfigurator implements DirectoryProjectConfigurator {
+ private static final Logger LOG = Logger.getInstance(PyIntegratedToolsProjectConfigurator.class);
+
@Override
- public void runActivity(@NotNull final Project project) {
+ public void configureProject(Project project, @NotNull VirtualFile baseDir, Ref<Module> moduleRef) {
final Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) {
return;
}
private static void updateIntegratedTools(final Module module, final int delay) {
+ final PyDocumentationSettings docSettings = PyDocumentationSettings.getInstance(module);
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
public void run() {
if (delay > 0) {
catch (InterruptedException ignore) {
}
}
+
+ LOG.debug("Integrated tools configurator has started");
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
- final TestRunnerService runnerService = TestRunnerService.getInstance(module);
- if (runnerService == null) return;
- final String configuration = runnerService.getProjectConfiguration();
- if (!StringUtil.isEmptyOrSpaces(configuration))
- return;
-
+ @NotNull DocStringFormat docFormat = DocStringFormat.PLAIN;
//check setup.py
- String testRunner = detectTestRunnerFromSetupPy(module);
+ @NotNull String testRunner = detectTestRunnerFromSetupPy(module);
+ if (!testRunner.isEmpty()) {
+ LOG.debug("Test runner '" + testRunner + "' was discovered from setup.py in the module '" + module.getModuleFilePath() + "'");
+ }
//try to find test_runner import
- final Collection<VirtualFile> filenames = FilenameIndex.getAllFilesByExt(module.getProject(), PythonFileType.INSTANCE.getDefaultExtension(),
- GlobalSearchScope.moduleScope(module));
-
- for (VirtualFile file : filenames) {
+ final String extension = PythonFileType.INSTANCE.getDefaultExtension();
+ // Module#getModuleScope() and GlobalSearchScope#getModuleScope() search only in source roots
+ final GlobalSearchScope searchScope = module.getModuleContentScope();
+ final Collection<VirtualFile> pyFiles = FilenameIndex.getAllFilesByExt(module.getProject(), extension, searchScope);
+ for (VirtualFile file : pyFiles) {
if (file.getName().startsWith("test")) {
- if (testRunner.isEmpty()) testRunner = checkImports(file, module); //find test runner import
+ if (testRunner.isEmpty()) {
+ testRunner = checkImports(file, module); //find test runner import
+ if (!testRunner.isEmpty()) {
+ LOG.debug("Test runner '" + testRunner + "' was detected from imports in the file '" + file.getPath() + "'");
+ }
+ }
}
- else {
- // FIXME: Find a better way to run this updater only once when new project from existing sources is created
- if (PyDocumentationSettings.getInstance(module).getFormat() == null) {
- checkDocstring(file, module); // detect docstring type
+ else if (docFormat == DocStringFormat.PLAIN) {
+ docFormat = checkDocstring(file, module); // detect docstring type
+ if (docFormat != DocStringFormat.PLAIN) {
+ LOG.debug("Docstring format '" + docFormat + "' was detected from content of the file '" + file.getPath() + "'");
}
}
- if (!testRunner.isEmpty() && PyDocumentationSettings.getInstance(module).getFormat() != null) {
+
+ if (!testRunner.isEmpty() && docFormat != DocStringFormat.PLAIN) {
break;
}
}
+
+ // Check test runners available in the module SDK
if (testRunner.isEmpty()) {
//check if installed in sdk
final Sdk sdk = PythonSdkType.findPythonSdk(module);
- if (sdk != null && sdk.getSdkType() instanceof PythonSdkType && testRunner.isEmpty()) {
+ if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) {
final Boolean nose = VFSTestFrameworkListener.isTestFrameworkInstalled(sdk, PyNames.NOSE_TEST);
final Boolean pytest = VFSTestFrameworkListener.isTestFrameworkInstalled(sdk, PyNames.PY_TEST);
final Boolean attest = VFSTestFrameworkListener.isTestFrameworkInstalled(sdk, PyNames.AT_TEST);
testRunner = PythonTestConfigurationsModel.PY_TEST_NAME;
else if (attest != null && attest)
testRunner = PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME;
+ if (!testRunner.isEmpty()) {
+ LOG.debug("Test runner '" + testRunner + "' was detected from SDK " + sdk);
+ }
}
}
- if (StringUtil.isEmptyOrSpaces(testRunner))
- testRunner = PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME;
+ final TestRunnerService runnerService = TestRunnerService.getInstance(module);
+ if (runnerService != null) {
+ if (testRunner.isEmpty()) {
+ runnerService.setProjectConfiguration(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME);
+ }
+ else {
+ runnerService.setProjectConfiguration(testRunner);
+ LOG.info("Test runner '" + testRunner + "' was detected by project configurator");
+ }
+ }
- runnerService.setProjectConfiguration(testRunner);
- if (PyDocumentationSettings.getInstance(module).getFormat() == null) {
- PyDocumentationSettings.getInstance(module).setFormat(DocStringFormat.PLAIN);
+ // Documentation settings should have meaningful default already
+ if (docFormat != DocStringFormat.PLAIN) {
+ docSettings.setFormat(docFormat);
+ LOG.info("Docstring format '" + docFormat + "' was detected by project configurator");
}
}
}, ModalityState.any(), module.getDisposed());
});
}
- private static String detectTestRunnerFromSetupPy(Module module) {
- String testRunner = "";
- if (!testRunner.isEmpty()) return testRunner;
+ @NotNull
+ private static String detectTestRunnerFromSetupPy(@NotNull Module module) {
final PyFile setupPy = PyPackageUtil.findSetupPy(module);
- if (setupPy == null)
- return testRunner;
+ if (setupPy == null) return "";
final PyCallExpression setupCall = PyPackageUtil.findSetupCall(setupPy);
- if (setupCall == null)
- return testRunner;
+ if (setupCall == null) return "";
for (PyExpression arg : setupCall.getArguments()) {
if (arg instanceof PyKeywordArgument) {
final PyKeywordArgument kwarg = (PyKeywordArgument)arg;
if (value instanceof PyStringLiteralExpression) {
final String stringValue = ((PyStringLiteralExpression)value).getStringValue();
if (stringValue.contains(PyNames.NOSE_TEST)) {
- testRunner = PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME;
- break;
+ return PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME;
}
if (stringValue.contains(PyNames.PY_TEST)) {
- testRunner = PythonTestConfigurationsModel.PY_TEST_NAME;
- break;
+ return PythonTestConfigurationsModel.PY_TEST_NAME;
}
if (stringValue.contains(PyNames.AT_TEST_IMPORT)) {
- testRunner = PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME;
- break;
+ return PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME;
}
}
}
}
}
- return testRunner;
+ return "";
}
- private static void checkDocstring(VirtualFile file, Module module) {
- final PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(module);
+ @NotNull
+ private static DocStringFormat checkDocstring(@NotNull VirtualFile file, @NotNull Module module) {
final PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(file);
if (psiFile instanceof PyFile) {
- if (documentationSettings.isEpydocFormat(psiFile))
- documentationSettings.setFormat(DocStringFormat.EPYTEXT);
- else if (documentationSettings.isReSTFormat(psiFile))
- documentationSettings.setFormat(DocStringFormat.REST);
- else {
- final String fileText = psiFile.getText();
- if (!fileText.contains(":param ") && !fileText.contains(":type ") && !fileText.contains(":rtype ") &&
- !fileText.contains("@param ") && !fileText.contains("@type ") && !fileText.contains("@rtype ")) return;
-
- final PyDocStringOwner[] childrens = PsiTreeUtil.getChildrenOfType(psiFile, PyDocStringOwner.class);
- if (childrens != null) {
- for (PyDocStringOwner owner : childrens) {
- final PyStringLiteralExpression docStringExpression = owner.getDocStringExpression();
- if (docStringExpression != null) {
- String text = docStringExpression.getStringValue();
- if (text.contains(":param ") || text.contains(":rtype") || text.contains(":type")) {
- documentationSettings.setFormat(DocStringFormat.REST);
- return;
- }
- else if (text.contains("@param ") || text.contains("@rtype") || text.contains("@type")) {
- documentationSettings.setFormat(DocStringFormat.EPYTEXT);
- return;
- }
+ final DocStringFormat perFileFormat = PyDocumentationSettings.getFormatFromDocformatAttribute(psiFile);
+ if (perFileFormat != null) {
+ return perFileFormat;
+ }
+ // Why toplevel docstring owners only
+ final PyDocStringOwner[] children = PsiTreeUtil.getChildrenOfType(psiFile, PyDocStringOwner.class);
+ if (children != null) {
+ for (PyDocStringOwner owner : children) {
+ final PyStringLiteralExpression docStringExpression = owner.getDocStringExpression();
+ if (docStringExpression != null) {
+ final DocStringFormat guessed = DocStringUtil.guessDocStringFormat(docStringExpression.getStringValue());
+ if (guessed != DocStringFormat.PLAIN) {
+ return guessed;
}
}
}
}
}
+ return DocStringFormat.PLAIN;
}
- private static String checkImports(VirtualFile file, Module module) {
+ @NotNull
+ private static String checkImports(@NotNull VirtualFile file, @NotNull Module module) {
final PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(file);
if (psiFile instanceof PyFile) {
final List<PyImportElement> importTargets = ((PyFile)psiFile).getImportTargets();