SetupTask (PyFile file, @NotNull @NlsSafe 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, null); if (description != null) { final String descriptionText = PyPsiUtils.strValue(PyPsiUtils.flattenParens(description.findAssignedValue())); if (descriptionText != null) { task.setDescription(descriptionText); } } final List<PyExpression> booleanOptions = resolveSequenceValue(taskClass, "boolean_options"); final List<String> 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, null); final Map<String, String> negativeOptMap = negativeOpt == null ? Collections.emptyMap() : parseNegativeOpt(negativeOpt.findAssignedValue()); final List<PyExpression> 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; }