All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.packaging.setupPy.SetupTaskIntrospector Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 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.packaging.setupPy;

import com.google.common.collect.ImmutableSet;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.packaging.PyPackageUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 * @author yole
 */
public class SetupTaskIntrospector {
  private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.packaging.setupPy.SetupTaskIntrospector");

  private static final Map> ourDistutilsTaskCache = new HashMap>();
  private static final Map> ourSetuptoolsTaskCache = new HashMap>();

  private static boolean usesSetuptools(PyFile file) {
    final List imports = file.getFromImports();
    for (PyFromImportStatement anImport : imports) {
      final QualifiedName qName = anImport.getImportSourceQName();
      if (qName != null && qName.matches("setuptools")) {
        return true;
      }
    }

    final List importElements = file.getImportTargets();
    for (PyImportElement element : importElements) {
      final QualifiedName qName = element.getImportedQName();
      if (qName != null && qName.matches("setuptools")) {
        return true;
      }
    }
    return false;
  }

  @Nullable
  public static List getSetupTaskOptions(Module module, String taskName) {
    for (SetupTask task : getTaskList(module)) {
      if (task.getName().equals(taskName)) {
        return task.getOptions();
      }
    }
    return null;
  }

  public static List getTaskList(Module module) {
    final PyFile setupPy = PyPackageUtil.findSetupPy(module);
    return getTaskList(module, setupPy != null && usesSetuptools(setupPy));
  }

  private static List getTaskList(Module module, boolean setuptools) {
    final String name = (setuptools ? "setuptools" : "distutils") + ".command.install.install";
    final Map> cache = setuptools ? ourSetuptoolsTaskCache : ourDistutilsTaskCache;
    final PyClass installClass = PyClassNameIndex.findClass(name, module.getProject());
    if (installClass != null) {
      final PsiDirectory distutilsCommandDir = installClass.getContainingFile().getParent();
      if (distutilsCommandDir != null) {
        final String path = distutilsCommandDir.getVirtualFile().getPath();
        List tasks = cache.get(path);
        if (tasks == null) {
          tasks = collectTasks(distutilsCommandDir, setuptools);
          cache.put(path, tasks);
        }
        return tasks;
      }
    }
    return Collections.emptyList();
  }

  private static final Set SKIP_NAMES = ImmutableSet.of(PyNames.INIT_DOT_PY, "alias.py", "setopt.py", "savecfg.py");

  private static List collectTasks(PsiDirectory dir, boolean setuptools) {
    List result = new ArrayList();
    for (PsiFile commandFile : dir.getFiles()) {
      if (commandFile instanceof PyFile && !SKIP_NAMES.contains(commandFile.getName())) {
        final String taskName = FileUtil.getNameWithoutExtension(commandFile.getName());
        result.add(createTaskFromFile((PyFile)commandFile, taskName, setuptools));
      }
    }
    return result;
  }

  private static SetupTask createTaskFromFile(PyFile file, String name, boolean setuptools) {
    SetupTask task = new SetupTask(name);
    // setuptools wraps the build_ext command class in a way that we cannot understand; use the distutils class which it delegates to
    final PyClass taskClass = (name.equals("build_ext") && setuptools)
                              ? PyClassNameIndex.findClass("distutils.command.build_ext.build_ext", file.getProject())
                              : file.findTopLevelClass(name);
    if (taskClass != null) {
      final PyTargetExpression description = taskClass.findClassAttribute("description", true);
      if (description != null) {
        final String descriptionText = PyPsiUtils.strValue(PyPsiUtils.flattenParens(description.findAssignedValue()));
        if (descriptionText != null) {
          task.setDescription(descriptionText);
        }
      }

      final List booleanOptions = resolveSequenceValue(taskClass, "boolean_options");
      final List booleanOptionsList = new ArrayList();
      for (PyExpression option : booleanOptions) {
        final String s = PyPsiUtils.strValue(option);
        if (s != null) {
          booleanOptionsList.add(s);
        }
      }

      final PyTargetExpression negativeOpt = taskClass.findClassAttribute("negative_opt", true);
      final Map negativeOptMap = negativeOpt == null
                                                 ? Collections.emptyMap()
                                                 : parseNegativeOpt(negativeOpt.findAssignedValue());


      final List userOptions = resolveSequenceValue(taskClass, "user_options");
      for (PyExpression element : userOptions) {
        final SetupTask.Option option = createOptionFromTuple(element, booleanOptionsList, negativeOptMap);
        if (option != null) {
          task.addOption(option);
        }
      }
    }
    return task;
  }

  private static List resolveSequenceValue(PyClass aClass, String name) {
    List result = new ArrayList();
    collectSequenceElements(aClass.findClassAttribute(name, true), result);
    return result;
  }

  private static void collectSequenceElements(PsiElement value, List result) {
    if (value instanceof PySequenceExpression) {
      Collections.addAll(result, ((PySequenceExpression)value).getElements());
    }
    else if (value instanceof PyBinaryExpression) {
      final PyBinaryExpression binaryExpression = (PyBinaryExpression)value;
      if (binaryExpression.isOperator("+")) {
        collectSequenceElements(binaryExpression.getLeftExpression(), result);
        collectSequenceElements(binaryExpression.getRightExpression(), result);
      }
    }
    else if (value instanceof PyReferenceExpression) {
      final PsiElement resolveResult = ((PyReferenceExpression)value).getReference(PyResolveContext.noImplicits()).resolve();
      collectSequenceElements(resolveResult, result);
    }
    else if (value instanceof PyTargetExpression) {
      collectSequenceElements(((PyTargetExpression)value).findAssignedValue(), result);
    }
  }

  private static Map parseNegativeOpt(PyExpression dict) {
    Map result = new HashMap();
    dict = PyPsiUtils.flattenParens(dict);
    if (dict instanceof PyDictLiteralExpression) {
      final PyKeyValueExpression[] elements = ((PyDictLiteralExpression)dict).getElements();
      for (PyKeyValueExpression element : elements) {
        String key = PyPsiUtils.strValue(PyPsiUtils.flattenParens(element.getKey()));
        String value = PyPsiUtils.strValue(PyPsiUtils.flattenParens(element.getValue()));
        if (key != null && value != null) {
          result.put(key, value);
        }
      }
    }
    return result;
  }

  @Nullable
  private static SetupTask.Option createOptionFromTuple(PyExpression tuple, List booleanOptions, Map negativeOptMap) {
    tuple = PyPsiUtils.flattenParens(tuple);
    if (tuple instanceof PyTupleExpression) {
      final PyExpression[] elements = ((PyTupleExpression)tuple).getElements();
      if (elements.length == 3) {
        String name = PyPsiUtils.strValue(elements[0]);
        final String description = PyPsiUtils.strValue(elements[2]);
        if (name != null && description != null) {
          if (negativeOptMap.containsKey(name)) {
            return null;
          }
          if (description.contains("don't use") || description.contains("deprecated")) {
            return null;
          }
          final boolean checkbox = booleanOptions.contains(name);
          boolean negative = false;
          if (negativeOptMap.containsValue(name)) {
            negative = true;
            for (Map.Entry entry : negativeOptMap.entrySet()) {
              if (entry.getValue().equals(name)) {
                name = entry.getKey();
                break;
              }
            }
          }
          return new SetupTask.Option(name, StringUtil.capitalize(description), checkbox, negative);
        }
      }
    }
    return null;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy