mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Apply deprecation notes in packaging
Make PyRequirementVersionSpec an interface Make PyRequirement an interface Add documentation to packaging classes that could be useful for other developers
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package org.jetbrains.plugins.ipnb.run;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.Executor;
|
||||
import com.intellij.execution.configurations.ConfigurationFactory;
|
||||
@@ -23,7 +22,7 @@ import com.intellij.remote.RemoteSdkCredentialsHolder;
|
||||
import com.jetbrains.python.packaging.PyPackage;
|
||||
import com.jetbrains.python.packaging.PyPackageManager;
|
||||
import com.jetbrains.python.packaging.PyPackageUtil;
|
||||
import com.jetbrains.python.packaging.PyRequirement;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.run.AbstractPythonRunConfiguration;
|
||||
import com.jetbrains.python.run.DebugAwareConfiguration;
|
||||
@@ -32,8 +31,12 @@ import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.packaging.PyRequirementsKt.pyRequirement;
|
||||
|
||||
public class IpnbRunConfiguration extends AbstractPythonRunConfiguration<IpnbRunConfiguration> implements DebugAwareConfiguration {
|
||||
|
||||
@NonNls private static final String ATTR_ADDITIONAL_OPTIONS = "additionalOptions";
|
||||
@@ -95,9 +98,12 @@ public class IpnbRunConfiguration extends AbstractPythonRunConfiguration<IpnbRun
|
||||
if (RemoteSdkCredentialsHolder.isRemoteSdk(sdk.getHomePath())) {
|
||||
throw new RuntimeConfigurationError("Please select local python interpreter");
|
||||
}
|
||||
final List<PyPackage> packages = PyPackageManager.getInstance(sdk).getPackages();
|
||||
final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
|
||||
final List<PyPackage> packages = packageManager.getPackages();
|
||||
|
||||
final PyPackage ipythonPackage = packages != null ? PyPackageUtil.findPackage(packages, "ipython") : null;
|
||||
final PyPackage jupyterPackage = packages != null ? PyPackageUtil.findPackage(packages, "jupyter") : null;
|
||||
|
||||
if (ipythonPackage == null && jupyterPackage == null) {
|
||||
throw new RuntimeConfigurationError("Install Jupyter Notebook to the interpreter of the current project.",
|
||||
() -> ProgressManager.getInstance().run(new Task.Backgroundable(getProject(),
|
||||
@@ -109,12 +115,16 @@ public class IpnbRunConfiguration extends AbstractPythonRunConfiguration<IpnbRun
|
||||
if (version != null) {
|
||||
final LanguageLevel level = LanguageLevel.fromPythonVersion(version);
|
||||
if (level.isAtLeast(LanguageLevel.PYTHON33)) {
|
||||
PyPackageManager.getInstance(sdk).install("jupyter");
|
||||
packageManager.install("jupyter");
|
||||
}
|
||||
else {
|
||||
PyPackageManager.getInstance(sdk).install(Lists.newArrayList(
|
||||
PyRequirement.fromLine("ipython==5"), PyRequirement.fromLine("jupyter")),
|
||||
Lists.newArrayList());
|
||||
packageManager.install(
|
||||
Arrays.asList(
|
||||
pyRequirement("ipython", PyRequirementRelation.EQ, "5"),
|
||||
pyRequirement("jupyter")
|
||||
),
|
||||
Collections.emptyList()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.messages.Topic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -62,9 +63,39 @@ public abstract class PyPackageManager {
|
||||
@Nullable
|
||||
public abstract List<PyRequirement> getRequirements(@NotNull Module module);
|
||||
|
||||
/**
|
||||
* @param line requirement description
|
||||
* @return parsed requirement or null if given description could not be parsed.
|
||||
* @see <a href="https://pip.pypa.io/en/stable/reference/pip_install/"><code>pip install</code> documentation</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0508/">PEP-508</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/">PEP-440</a>
|
||||
*/
|
||||
@Nullable
|
||||
public abstract PyRequirement parseRequirement(@NotNull String line);
|
||||
|
||||
/**
|
||||
* @param text requirements descriptions
|
||||
* @return parsed requirements.
|
||||
* <i>Note: the returned list does not contain null or repetitive values, descriptions that could not be parsed are skipped.</i>
|
||||
* @see <a href="https://pip.pypa.io/en/stable/reference/pip_install/"><code>pip install</code> documentation</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0508/">PEP-508</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/">PEP-440</a>
|
||||
*/
|
||||
@NotNull
|
||||
public abstract List<PyRequirement> parseRequirements(@NotNull String text);
|
||||
|
||||
/**
|
||||
* @param file file containing requirements descriptions.
|
||||
* Used as a foothold to resolve recursive requirements specified through <code>-r</code> or <code>--requirement</code> flags.
|
||||
* @return parsed requirements.
|
||||
* <i>Note: the returned list does not contain null or repetitive values, descriptions that could not be parsed are skipped.</i>
|
||||
* @see <a href="https://pip.pypa.io/en/stable/reference/pip_install/"><code>pip install</code> documentation</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0508/">PEP-508</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/">PEP-440</a>
|
||||
*/
|
||||
@NotNull
|
||||
public abstract List<PyRequirement> parseRequirements(@NotNull VirtualFile file);
|
||||
|
||||
@NotNull
|
||||
public abstract Set<PyPackage> getDependents(@NotNull PyPackage pkg) throws ExecutionException;
|
||||
|
||||
|
||||
@@ -13,865 +13,59 @@
|
||||
// limitations under the License.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersion;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @apiNote This class will be converted to interface in 2018.2.
|
||||
* @see <a href="https://pip.pypa.io/en/stable/reference/pip_install/"><code>pip install</code> documentation</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0508/">PEP-508</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/">PEP-440</a>
|
||||
* @see PyPackageManager#parseRequirement(String)
|
||||
* @see PyPackageManager#parseRequirements(String)
|
||||
* @see PyPackageManager#parseRequirements(VirtualFile)
|
||||
*/
|
||||
public class PyRequirement {
|
||||
|
||||
// common regular expressions
|
||||
public interface PyRequirement {
|
||||
|
||||
@NotNull
|
||||
private static final String LINE_WS_REGEXP = "[ \t]";
|
||||
String getName();
|
||||
|
||||
@NotNull
|
||||
private static final String COMMENT_GROUP = "comment";
|
||||
|
||||
@NotNull
|
||||
private static final String COMMENT_REGEXP = "(?<" + COMMENT_GROUP + ">" + LINE_WS_REGEXP + "+#.*)?";
|
||||
|
||||
@NotNull
|
||||
private static final String NAME_GROUP = "name";
|
||||
|
||||
// PEP-508
|
||||
// https://www.python.org/dev/peps/pep-0508/
|
||||
|
||||
@NotNull
|
||||
private static final String IDENTIFIER_REGEXP = "[A-Za-z0-9]([-_\\.]?[A-Za-z0-9])*";
|
||||
|
||||
@NotNull
|
||||
private static final String NAME_REGEXP = "(?<" + NAME_GROUP + ">" + IDENTIFIER_REGEXP + ")";
|
||||
|
||||
@NotNull
|
||||
private static final String EXTRAS_REGEXP =
|
||||
"\\[" + IDENTIFIER_REGEXP + "(" + LINE_WS_REGEXP + "*," + LINE_WS_REGEXP + "*" + IDENTIFIER_REGEXP + ")*" + "\\]";
|
||||
|
||||
// archive-related regular expressions
|
||||
|
||||
@NotNull
|
||||
private static final Pattern GITHUB_ARCHIVE_URL =
|
||||
Pattern.compile("https?://github\\.com/[^/\\s]+/(?<" + NAME_GROUP + ">[^/\\s]+)/archive/\\S+" + COMMENT_REGEXP);
|
||||
|
||||
@NotNull
|
||||
private static final Pattern GITLAB_ARCHIVE_URL =
|
||||
Pattern.compile("https?://gitlab\\.com/[^/\\s]+/(?<" + NAME_GROUP + ">[^/\\s]+)/repository/\\S+" + COMMENT_REGEXP);
|
||||
|
||||
@NotNull
|
||||
private static final Pattern ARCHIVE_URL =
|
||||
Pattern.compile("https?://\\S+/" +
|
||||
"(?<" + NAME_GROUP + ">\\S+)" +
|
||||
"(\\.tar\\.gz|\\.zip)(#(sha1|sha224|sha256|sha384|sha512|md5)=\\w+)?" + COMMENT_REGEXP);
|
||||
|
||||
// vcs-related regular expressions
|
||||
// don't forget to update calculateVcsInstallOptions(Matcher) after this section changing
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EDITABLE_GROUP = "editable";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EDITABLE_REGEXP = "((?<" + VCS_EDITABLE_GROUP + ">-e|--editable)" + LINE_WS_REGEXP + "+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_BEFORE_GROUP = "srcb";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_AFTER_GROUP = "srca";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_BEFORE_REGEXP =
|
||||
"(?<" + VCS_SRC_BEFORE_GROUP + ">--src" + LINE_WS_REGEXP + "+\\S+" + LINE_WS_REGEXP + "+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_AFTER_REGEXP =
|
||||
"(?<" + VCS_SRC_AFTER_GROUP + ">" + LINE_WS_REGEXP + "+--src" + LINE_WS_REGEXP + "+\\S+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String PATH_IN_VCS_GROUP = "path";
|
||||
|
||||
@NotNull
|
||||
private static final String PATH_IN_VCS_REGEXP = "(?<" + PATH_IN_VCS_GROUP + ">[^@#\\s]+)";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_REVISION_REGEXP = "(@[^#\\s]+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EGG_BEFORE_SUBDIR_GROUP = "eggb";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EGG_AFTER_SUBDIR_GROUP = "egga";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EXTRAS_BEFORE_SUBDIR_GROUP = "extrasb";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EXTRAS_AFTER_SUBDIR_GROUP = "extrasa";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_PARAMS_REGEXP =
|
||||
"(" +
|
||||
"(" +
|
||||
"#egg=(?<" + VCS_EGG_BEFORE_SUBDIR_GROUP + ">[^&\\s\\[\\]]+)(?<" + VCS_EXTRAS_BEFORE_SUBDIR_GROUP + ">" + EXTRAS_REGEXP + ")?" +
|
||||
"(&subdirectory=\\S+)?" +
|
||||
")" +
|
||||
"|" +
|
||||
"(" +
|
||||
"#subdirectory=[^&\\s]+" +
|
||||
"&egg=(?<" + VCS_EGG_AFTER_SUBDIR_GROUP + ">[^\\s\\[\\]]+)(?<" + VCS_EXTRAS_AFTER_SUBDIR_GROUP + ">" + EXTRAS_REGEXP + ")?" +
|
||||
")" +
|
||||
")?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_GROUP = "vcs";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_URL_PREFIX = VCS_SRC_BEFORE_REGEXP + VCS_EDITABLE_REGEXP + "(?<" + VCS_GROUP + ">";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_URL_SUFFIX =
|
||||
PATH_IN_VCS_REGEXP + VCS_REVISION_REGEXP + VCS_PARAMS_REGEXP + ")" + VCS_SRC_AFTER_REGEXP + COMMENT_REGEXP;
|
||||
|
||||
@NotNull
|
||||
private static final String GIT_USER_AT_REGEXP = "[\\w-]+@";
|
||||
|
||||
// supports: git+user@...
|
||||
@NotNull
|
||||
private static final Pattern GIT_PROJECT_URL =
|
||||
Pattern.compile(VCS_URL_PREFIX + "git\\+" + GIT_USER_AT_REGEXP + "[^:\\s]+:" + VCS_URL_SUFFIX);
|
||||
|
||||
// supports: bzr+lp:...
|
||||
@NotNull
|
||||
private static final Pattern BZR_PROJECT_URL = Pattern.compile(VCS_URL_PREFIX + "bzr\\+lp:" + VCS_URL_SUFFIX);
|
||||
|
||||
// supports: (bzr|git|hg|svn)(+smth)?://...
|
||||
@NotNull
|
||||
private static final Pattern VCS_PROJECT_URL =
|
||||
Pattern.compile(VCS_URL_PREFIX + "(bzr|git|hg|svn)(\\+[A-Za-z]+)?://?[^/]+/" + VCS_URL_SUFFIX);
|
||||
|
||||
// requirement-related regular expressions
|
||||
// don't forget to update calculateRequirementInstallOptions(Matcher) after this section changing
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_EXTRAS_GROUP = "extras";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_EXTRAS_REGEXP = "(?<" + REQUIREMENT_EXTRAS_GROUP + ">" + EXTRAS_REGEXP + ")?";
|
||||
|
||||
// PEP-440
|
||||
// https://www.python.org/dev/peps/pep-0440/
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_VERSIONS_SPECS_GROUP = "versionspecs";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_VERSION_SPEC_REGEXP = "(<=?|!=|===?|>=?|~=)" + LINE_WS_REGEXP + "*[\\.\\*\\+!\\w-]+";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_VERSIONS_SPECS_REGEXP =
|
||||
"(?<" + REQUIREMENT_VERSIONS_SPECS_GROUP + ">" + REQUIREMENT_VERSION_SPEC_REGEXP +
|
||||
"(" + LINE_WS_REGEXP + "*," + LINE_WS_REGEXP + "*" + REQUIREMENT_VERSION_SPEC_REGEXP + ")*)?";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_OPTIONS_GROUP = "options";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_OPTIONS_REGEXP =
|
||||
"(?<" + REQUIREMENT_OPTIONS_GROUP + ">(" + LINE_WS_REGEXP + "+(--global-option|--install-option)=\"[^\"]*\")+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_GROUP = "requirement";
|
||||
|
||||
@NotNull
|
||||
private static final Pattern REQUIREMENT = Pattern.compile(
|
||||
"(?<" + REQUIREMENT_GROUP + ">" +
|
||||
NAME_REGEXP +
|
||||
LINE_WS_REGEXP + "*" +
|
||||
REQUIREMENT_EXTRAS_REGEXP +
|
||||
LINE_WS_REGEXP + "*" +
|
||||
REQUIREMENT_VERSIONS_SPECS_REGEXP +
|
||||
")" +
|
||||
REQUIREMENT_OPTIONS_REGEXP +
|
||||
COMMENT_REGEXP);
|
||||
|
||||
@NotNull
|
||||
private final String myName;
|
||||
|
||||
@NotNull
|
||||
private final List<PyRequirementVersionSpec> myVersionSpecs;
|
||||
|
||||
@NotNull
|
||||
private final List<String> myInstallOptions;
|
||||
|
||||
@NotNull
|
||||
private final String myExtras;
|
||||
List<PyRequirementVersionSpec> getVersionSpecs();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name) {
|
||||
this(name, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name, @NotNull PyRequirementRelation relation, @NotNull String version) {
|
||||
this(name, Collections.singletonList(calculateVersionSpec(version, relation)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name, @NotNull String version) {
|
||||
this(name, PyRequirementRelation.EQ, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name, @NotNull String version, @NotNull List<String> installOptions) {
|
||||
this(name, Collections.singletonList(calculateVersionSpec(version, PyRequirementRelation.EQ)), installOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name, @NotNull String version, @NotNull List<String> installOptions, @NotNull String extras) {
|
||||
this(name, Collections.singletonList(calculateVersionSpec(version, PyRequirementRelation.EQ)), installOptions, extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name, @NotNull List<PyRequirementVersionSpec> versionSpecs) {
|
||||
myName = name;
|
||||
myVersionSpecs = versionSpecs;
|
||||
myExtras = "";
|
||||
myInstallOptions = Collections.singletonList(toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name, @NotNull List<PyRequirementVersionSpec> versionSpecs, @NotNull List<String> installOptions) {
|
||||
myName = name;
|
||||
myVersionSpecs = versionSpecs;
|
||||
myInstallOptions = Collections.unmodifiableList(installOptions);
|
||||
myExtras = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public PyRequirement(@NotNull String name,
|
||||
@NotNull List<PyRequirementVersionSpec> versionSpecs,
|
||||
@NotNull List<String> installOptions,
|
||||
@NotNull String extras) {
|
||||
myName = name;
|
||||
myVersionSpecs = versionSpecs;
|
||||
myInstallOptions = Collections.unmodifiableList(installOptions);
|
||||
myExtras = extras;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in 2018.2.
|
||||
* @return list of options to pass to <code>pip install</code>.
|
||||
* <i>
|
||||
* Note:
|
||||
* if list has more than one element it means that
|
||||
* <code>--src</code>, <code>-e</code>, <code>--editable</code>, <code>--global-option</code> or <code>--install-option</code>
|
||||
* options are used
|
||||
* </i>.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public PyRequirement withVersionComparator(@NotNull Comparator<String> comparator) {
|
||||
return new PyRequirement(myName,
|
||||
ContainerUtil.map(myVersionSpecs, spec -> spec.withVersionComparator(comparator)), myInstallOptions, myExtras);
|
||||
}
|
||||
List<String> getInstallOptions();
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
String getExtras();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyRequirement#getName()} and {@link PyRequirement#getExtras()} instead.
|
||||
* This method will be removed in 2018.2.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public String getFullName() {
|
||||
return myName + myExtras;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExtras() {
|
||||
return myExtras;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<PyRequirementVersionSpec> getVersionSpecs() {
|
||||
return myVersionSpecs;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getInstallOptions() {
|
||||
return myInstallOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myName + myExtras + StringUtil.join(myVersionSpecs, ",");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
final PyRequirement that = (PyRequirement)o;
|
||||
|
||||
if (!myName.equals(that.myName)) return false;
|
||||
if (!myVersionSpecs.equals(that.myVersionSpecs)) return false;
|
||||
if (!myInstallOptions.equals(that.myInstallOptions)) return false;
|
||||
if (!myExtras.equals(that.myExtras)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myName.hashCode();
|
||||
result = 31 * result + myVersionSpecs.hashCode();
|
||||
result = 31 * result + myInstallOptions.hashCode();
|
||||
result = 31 * result + myExtras.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyPackage match(@NotNull List<PyPackage> packages) {
|
||||
final String normalizedName = normalizeName(myName);
|
||||
|
||||
return packages
|
||||
.stream()
|
||||
.filter(pkg -> normalizedName.equalsIgnoreCase(pkg.getName()))
|
||||
.findAny()
|
||||
.filter(pkg -> myVersionSpecs.stream().allMatch(spec -> spec.matches(pkg.getVersion())))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This method will be removed in 2018.2.
|
||||
* @param packages packages to match
|
||||
* @return first package that satisfies this requirement or null.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public static PyRequirement fromLine(@NotNull String line) {
|
||||
final PyRequirement githubArchiveUrl = parseGitArchiveUrl(GITHUB_ARCHIVE_URL, line);
|
||||
if (githubArchiveUrl != null) {
|
||||
return githubArchiveUrl;
|
||||
}
|
||||
|
||||
final PyRequirement gitlabArchiveUrl = parseGitArchiveUrl(GITLAB_ARCHIVE_URL, line);
|
||||
if (gitlabArchiveUrl != null) {
|
||||
return gitlabArchiveUrl;
|
||||
}
|
||||
|
||||
final PyRequirement archiveUrl = parseArchiveUrl(line);
|
||||
if (archiveUrl != null) {
|
||||
return archiveUrl;
|
||||
}
|
||||
|
||||
final PyRequirement vcsProjectUrl = parseVcsProjectUrl(line);
|
||||
if (vcsProjectUrl != null) {
|
||||
return vcsProjectUrl;
|
||||
}
|
||||
|
||||
return parseRequirement(line);
|
||||
}
|
||||
PyPackage match(@NotNull Collection<? extends PyPackage> packages);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This method will be removed in 2018.2.
|
||||
* @return concatenated representation of name, extras and version specs so it could be easily displayed.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public static List<PyRequirement> fromText(@NotNull String text) {
|
||||
return fromText(text, null, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyPackageManager#parseRequirements(String)} instead.
|
||||
* This method will be removed in 2018.2.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public static List<PyRequirement> fromFile(@NotNull VirtualFile file) {
|
||||
return fromText(loadText(file), file, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link PyRequirement} instead.
|
||||
* This method will be removed in 2018.2.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public static PyRequirementVersionSpec calculateVersionSpec(@NotNull String version, @NotNull PyRequirementRelation expectedRelation) {
|
||||
if (expectedRelation == PyRequirementRelation.STR_EQ) return new PyRequirementVersionSpec(version);
|
||||
|
||||
final PyRequirementVersion normalizedVersion = PyRequirementVersionNormalizer.normalize(version);
|
||||
|
||||
return normalizedVersion == null ?
|
||||
new PyRequirementVersionSpec(version) :
|
||||
new PyRequirementVersionSpec(expectedRelation, normalizedVersion);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseGitArchiveUrl(@NotNull Pattern pattern, @NotNull String line) {
|
||||
final Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return new PyRequirement(matcher.group(NAME_GROUP), Collections.emptyList(), Collections.singletonList(dropComments(line, matcher)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseArchiveUrl(@NotNull String line) {
|
||||
final Matcher matcher = ARCHIVE_URL.matcher(line);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return createVcsOrArchiveRequirement(parseNameAndVersionFromVcsOrArchive(matcher.group(NAME_GROUP)),
|
||||
Collections.singletonList(dropComments(line, matcher)),
|
||||
null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseVcsProjectUrl(@NotNull String line) {
|
||||
final Matcher vcsMatcher = VCS_PROJECT_URL.matcher(line);
|
||||
if (vcsMatcher.matches()) {
|
||||
return createVcsRequirement(vcsMatcher);
|
||||
}
|
||||
|
||||
final Matcher gitMatcher = GIT_PROJECT_URL.matcher(line);
|
||||
if (gitMatcher.matches()) {
|
||||
return createVcsRequirement(gitMatcher);
|
||||
}
|
||||
|
||||
final Matcher bzrMatcher = BZR_PROJECT_URL.matcher(line);
|
||||
if (bzrMatcher.matches()) {
|
||||
return createVcsRequirement(bzrMatcher);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseRequirement(@NotNull String line) {
|
||||
final Matcher matcher = REQUIREMENT.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
final String name = matcher.group(NAME_GROUP);
|
||||
final List<PyRequirementVersionSpec> versionSpecs = parseVersionSpecs(matcher.group(REQUIREMENT_VERSIONS_SPECS_GROUP));
|
||||
final List<String> installOptions = calculateRequirementInstallOptions(matcher);
|
||||
final String extras = matcher.group(REQUIREMENT_EXTRAS_GROUP);
|
||||
|
||||
if (extras == null) {
|
||||
return new PyRequirement(name, versionSpecs, installOptions);
|
||||
}
|
||||
else {
|
||||
return new PyRequirement(name, versionSpecs, installOptions, extras);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> fromText(@NotNull String text,
|
||||
@Nullable VirtualFile containingFile,
|
||||
@NotNull Set<VirtualFile> visitedFiles) {
|
||||
if (containingFile != null) {
|
||||
visitedFiles.add(containingFile);
|
||||
}
|
||||
|
||||
return StreamEx
|
||||
.of(splitByLinesAndCollapse(text))
|
||||
.flatCollection(line -> parseLine(line, containingFile, visitedFiles))
|
||||
.nonNull()
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String loadText(@NotNull VirtualFile file) {
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(file);
|
||||
|
||||
return document == null ? "" : document.getText();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String dropComments(@NotNull String line, @NotNull Matcher matcher) {
|
||||
final int commentIndex = matcher.start(COMMENT_GROUP);
|
||||
|
||||
if (commentIndex == -1) {
|
||||
return line;
|
||||
}
|
||||
|
||||
return line.substring(0, findFirstNotWhiteSpaceBefore(line, commentIndex) + 1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<String, String> parseNameAndVersionFromVcsOrArchive(@NotNull String name) {
|
||||
boolean isName = true;
|
||||
final List<String> nameParts = new ArrayList<>();
|
||||
final List<String> versionParts = new ArrayList<>();
|
||||
|
||||
for (String part : StringUtil.split(name, "-")) {
|
||||
final boolean partStartsWithDigit = !part.isEmpty() && Character.isDigit(part.charAt(0));
|
||||
|
||||
if (partStartsWithDigit || "dev".equals(part)) {
|
||||
isName = false;
|
||||
}
|
||||
|
||||
if (isName) {
|
||||
nameParts.add(part);
|
||||
}
|
||||
else {
|
||||
versionParts.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return Pair.create(normalizeVcsOrArchiveNameParts(nameParts), normalizeVcsOrArchiveVersionParts(versionParts));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyRequirement createVcsOrArchiveRequirement(@NotNull Pair<String, String> nameAndVersion,
|
||||
@NotNull List<String> installOptions,
|
||||
@Nullable String extras) {
|
||||
final String name = nameAndVersion.getFirst();
|
||||
final String version = nameAndVersion.getSecond();
|
||||
|
||||
if (version == null) {
|
||||
if (extras == null) {
|
||||
return new PyRequirement(name, Collections.emptyList(), installOptions);
|
||||
}
|
||||
else {
|
||||
return new PyRequirement(name, Collections.emptyList(), installOptions, extras);
|
||||
}
|
||||
}
|
||||
|
||||
final List<PyRequirementVersionSpec> versionSpecs = Collections.singletonList(calculateVersionSpec(version, PyRequirementRelation.EQ));
|
||||
if (extras == null) {
|
||||
return new PyRequirement(name, versionSpecs, installOptions);
|
||||
}
|
||||
else {
|
||||
return new PyRequirement(name, versionSpecs, installOptions, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyRequirement createVcsRequirement(@NotNull Matcher matcher) {
|
||||
final String path = matcher.group(PATH_IN_VCS_GROUP);
|
||||
final String egg = getEgg(matcher);
|
||||
|
||||
final String project = extractProject(dropTrunk(dropRevision(path)));
|
||||
final Pair<String, String> nameAndVersion =
|
||||
parseNameAndVersionFromVcsOrArchive(egg == null ? StringUtil.trimEnd(project, ".git") : egg);
|
||||
|
||||
return createVcsOrArchiveRequirement(nameAndVersion, calculateVcsInstallOptions(matcher), getVcsExtras(matcher));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirementVersionSpec> parseVersionSpecs(@Nullable String versionSpecs) {
|
||||
if (versionSpecs == null) return Collections.emptyList();
|
||||
|
||||
return StreamSupport
|
||||
.stream(StringUtil.tokenize(versionSpecs, ",").spliterator(), false)
|
||||
.map(String::trim)
|
||||
.map(PyRequirement::parseVersionSpec)
|
||||
.filter(req -> req != null)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> calculateRequirementInstallOptions(@NotNull Matcher matcher) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
result.add(matcher.group(REQUIREMENT_GROUP));
|
||||
|
||||
final String requirementOptions = matcher.group(REQUIREMENT_OPTIONS_GROUP);
|
||||
if (requirementOptions != null) {
|
||||
boolean isKey = true;
|
||||
for (String token : StringUtil.tokenize(requirementOptions, "\"")) {
|
||||
result.add(isKey ? token.substring(findFirstNotWhiteSpaceAfter(token, 0), token.length() - 1) : token);
|
||||
isKey = !isKey;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> splitByLinesAndCollapse(@NotNull String text) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String line : StringUtil.splitByLines(text)) {
|
||||
if (line.endsWith("\\") && !line.endsWith("\\\\")) {
|
||||
sb.append(line, 0, line.length() - 1);
|
||||
}
|
||||
else {
|
||||
if (sb.length() == 0) {
|
||||
result.add(line);
|
||||
}
|
||||
else {
|
||||
sb.append(line);
|
||||
|
||||
result.add(sb.toString());
|
||||
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> parseLine(@NotNull String line,
|
||||
@Nullable VirtualFile containingFile,
|
||||
@NotNull Set<VirtualFile> visitedFiles) {
|
||||
if (line.startsWith("-r")) {
|
||||
return parseRecursiveLine(line, containingFile, visitedFiles, "-r".length());
|
||||
}
|
||||
|
||||
if (line.startsWith("--requirement ")) {
|
||||
return parseRecursiveLine(line, containingFile, visitedFiles, "--requirement ".length());
|
||||
}
|
||||
|
||||
return Collections.singletonList(fromLine(line));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String normalizeVcsOrArchiveNameParts(@NotNull List<String> nameParts) {
|
||||
return normalizeName(StringUtil.join(nameParts, "-"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String normalizeVcsOrArchiveVersionParts(@NotNull List<String> versionParts) {
|
||||
return versionParts.isEmpty() ? null : normalizeVersion(StringUtil.join(versionParts, "-"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> calculateVcsInstallOptions(@NotNull Matcher matcher) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
|
||||
final String srcBefore = matcher.group(VCS_SRC_BEFORE_GROUP);
|
||||
if (srcBefore != null) {
|
||||
result.addAll(Arrays.asList(srcBefore.split("\\s+")));
|
||||
}
|
||||
|
||||
final String editable = matcher.group(VCS_EDITABLE_GROUP);
|
||||
if (editable != null) {
|
||||
result.add(editable);
|
||||
}
|
||||
|
||||
result.add(matcher.group(VCS_GROUP));
|
||||
|
||||
final String srcAfter = matcher.group(VCS_SRC_AFTER_GROUP);
|
||||
if (srcAfter != null) {
|
||||
result.addAll(Arrays.asList(srcAfter.split("\\s+")).subList(1, 3)); // skip spaces before --src and get only two values
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getEgg(@NotNull Matcher matcher) {
|
||||
final String beforeSubdir = matcher.group(VCS_EGG_BEFORE_SUBDIR_GROUP);
|
||||
|
||||
return beforeSubdir == null ? matcher.group(VCS_EGG_AFTER_SUBDIR_GROUP) : beforeSubdir;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String extractProject(@NotNull String path) {
|
||||
final int end = path.endsWith("/") ? path.length() - 1 : path.length();
|
||||
final int slashIndex = path.lastIndexOf("/", end - 1);
|
||||
|
||||
if (slashIndex != -1) {
|
||||
return path.substring(slashIndex + 1, end);
|
||||
}
|
||||
|
||||
if (end != path.length()) {
|
||||
return path.substring(0, end);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String dropTrunk(@NotNull String path) {
|
||||
final String slashTrunk = "/trunk";
|
||||
|
||||
if (path.endsWith(slashTrunk)) {
|
||||
return path.substring(0, path.length() - slashTrunk.length());
|
||||
}
|
||||
|
||||
final String slashTrunkSlash = "/trunk/";
|
||||
|
||||
if (path.endsWith(slashTrunkSlash)) {
|
||||
return path.substring(0, path.length() - slashTrunkSlash.length());
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String dropRevision(@NotNull String path) {
|
||||
final int atIndex = path.lastIndexOf("@");
|
||||
|
||||
if (atIndex != -1) {
|
||||
return path.substring(0, atIndex);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getVcsExtras(@NotNull Matcher matcher) {
|
||||
final String beforeSubdir = matcher.group(VCS_EXTRAS_BEFORE_SUBDIR_GROUP);
|
||||
|
||||
return beforeSubdir == null ? matcher.group(VCS_EXTRAS_AFTER_SUBDIR_GROUP) : beforeSubdir;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirementVersionSpec parseVersionSpec(@NotNull String versionSpec) {
|
||||
PyRequirementRelation relation = null;
|
||||
|
||||
if (versionSpec.startsWith("===")) {
|
||||
relation = PyRequirementRelation.STR_EQ;
|
||||
}
|
||||
else if (versionSpec.startsWith("==")) {
|
||||
relation = PyRequirementRelation.EQ;
|
||||
}
|
||||
else if (versionSpec.startsWith("<=")) {
|
||||
relation = PyRequirementRelation.LTE;
|
||||
}
|
||||
else if (versionSpec.startsWith(">=")) {
|
||||
relation = PyRequirementRelation.GTE;
|
||||
}
|
||||
else if (versionSpec.startsWith("<")) {
|
||||
relation = PyRequirementRelation.LT;
|
||||
}
|
||||
else if (versionSpec.startsWith(">")) {
|
||||
relation = PyRequirementRelation.GT;
|
||||
}
|
||||
else if (versionSpec.startsWith("~=")) {
|
||||
relation = PyRequirementRelation.COMPATIBLE;
|
||||
}
|
||||
else if (versionSpec.startsWith("!=")) {
|
||||
relation = PyRequirementRelation.NE;
|
||||
}
|
||||
|
||||
if (relation != null) {
|
||||
final int versionIndex = findFirstNotWhiteSpaceAfter(versionSpec, relation.toString().length());
|
||||
return calculateVersionSpec(versionSpec.substring(versionIndex), relation);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> parseRecursiveLine(@NotNull String line,
|
||||
@Nullable VirtualFile containingFile,
|
||||
@NotNull Set<VirtualFile> visitedFiles,
|
||||
int flagLength) {
|
||||
if (containingFile == null) return Collections.emptyList();
|
||||
|
||||
final int pathIndex = findFirstNotWhiteSpaceAfter(line, flagLength);
|
||||
if (pathIndex == line.length()) return Collections.emptyList();
|
||||
|
||||
final String path = FileUtil.toSystemIndependentName(line.substring(pathIndex));
|
||||
final VirtualFile file = findRecursiveFile(containingFile, path);
|
||||
|
||||
if (file != null && !visitedFiles.contains(file)) {
|
||||
return fromText(loadText(file), file, visitedFiles);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String normalizeName(@NotNull String s) {
|
||||
return s.replace("_", "-");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String normalizeVersion(@NotNull String s) {
|
||||
return s.replace("_", "-").replaceAll("-?py[\\d\\.]+", "");
|
||||
}
|
||||
|
||||
private static int findFirstNotWhiteSpaceAfter(@NotNull String line, int beginIndex) {
|
||||
for (int i = beginIndex; i < line.length(); i++) {
|
||||
if (!StringUtil.isWhiteSpace(line.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return line.length();
|
||||
}
|
||||
|
||||
private static int findFirstNotWhiteSpaceBefore(@NotNull String line, int beginIndex) {
|
||||
for (int i = beginIndex; i >= 0; i--) {
|
||||
if (!StringUtil.isWhiteSpace(line.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static VirtualFile findRecursiveFile(@NotNull VirtualFile containingFile, @NotNull String path) {
|
||||
final VirtualFile dir = containingFile.getParent();
|
||||
if (dir == null) return null;
|
||||
|
||||
final VirtualFile file = dir.findFileByRelativePath(path);
|
||||
if (file != null) return file;
|
||||
|
||||
return LocalFileSystem.getInstance().findFileByPath(path);
|
||||
default String getPresentableText() {
|
||||
return getName() + getExtras() + StringUtil.join(getVersionSpecs(), PyRequirementVersionSpec::getPresentableText, ",");
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -15,6 +15,11 @@ package com.jetbrains.python.packaging.requirement;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/#version-specifiers">PEP-440</a>
|
||||
* @see PyRequirementVersionSpec
|
||||
* @see com.jetbrains.python.packaging.PyRequirement
|
||||
*/
|
||||
public enum PyRequirementRelation {
|
||||
|
||||
LT("<"),
|
||||
@@ -33,9 +38,11 @@ public enum PyRequirementRelation {
|
||||
myValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return representation of this relation that is used in <a href="https://www.python.org/dev/peps/pep-0440/#version-specifiers">PEP-440</a>.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
public String getPresentableText() {
|
||||
return myValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2000-2017 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.requirement
|
||||
|
||||
@Deprecated(message = "Use com.jetbrains.python.packaging.PyRequirement instead. This class will be removed in 2018.2.")
|
||||
data class PyRequirementVersion(val epoch: String? = null,
|
||||
val release: String,
|
||||
val pre: String? = null,
|
||||
val post: String? = null,
|
||||
val dev: String? = null,
|
||||
val local: String? = null) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun release(release: String): PyRequirementVersion = PyRequirementVersion(release = release)
|
||||
}
|
||||
|
||||
val presentableText: String
|
||||
get() =
|
||||
sequenceOf(epochPresentable(), release, pre, postPresentable(), devPresentable(), localPresentable())
|
||||
.filterNotNull()
|
||||
.joinToString(separator = "") { it }
|
||||
|
||||
private fun epochPresentable() = if (epoch == null) null else "$epoch!"
|
||||
private fun postPresentable() = if (post == null) null else ".$post"
|
||||
private fun devPresentable() = if (dev == null) null else ".$dev"
|
||||
private fun localPresentable() = if (local == null) null else "+$local"
|
||||
}
|
||||
+13
-153
@@ -1,172 +1,32 @@
|
||||
// Copyright 2000-2017 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.
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging.requirement;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.intellij.webcore.packaging.PackageVersionComparator.VERSION_COMPARATOR;
|
||||
|
||||
/**
|
||||
* @apiNote This class will be converted to interface in 2018.2.
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/">PEP-440</a>
|
||||
* @see com.jetbrains.python.packaging.PyRequirement
|
||||
* @see PyRequirementRelation
|
||||
*/
|
||||
public class PyRequirementVersionSpec {
|
||||
public interface PyRequirementVersionSpec {
|
||||
|
||||
@NotNull
|
||||
private final PyRequirementRelation myRelation;
|
||||
|
||||
@Nullable
|
||||
private final PyRequirementVersion myParsedVersion;
|
||||
PyRequirementRelation getRelation();
|
||||
|
||||
@NotNull
|
||||
private final String myVersion;
|
||||
|
||||
@NotNull
|
||||
private final Comparator<String> myVersionComparator;
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link com.jetbrains.python.packaging.PyRequirement} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
* @param version version to check
|
||||
* @return true if given version satisfies this version spec.
|
||||
*/
|
||||
public PyRequirementVersionSpec(@NotNull PyRequirementRelation relation, @NotNull PyRequirementVersion version) {
|
||||
this(relation, version, version.getPresentableText(), VERSION_COMPARATOR);
|
||||
}
|
||||
boolean matches(@NotNull String version);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link com.jetbrains.python.packaging.PyRequirement} instead.
|
||||
* This constructor will be removed in 2018.2.
|
||||
*/
|
||||
public PyRequirementVersionSpec(@NotNull String version) {
|
||||
this(PyRequirementRelation.STR_EQ, null, version, VERSION_COMPARATOR);
|
||||
}
|
||||
|
||||
private PyRequirementVersionSpec(@NotNull PyRequirementRelation relation,
|
||||
@Nullable PyRequirementVersion parsedVersion,
|
||||
@NotNull String version,
|
||||
@NotNull Comparator<String> versionComparator) {
|
||||
myRelation = relation;
|
||||
myParsedVersion = parsedVersion;
|
||||
myVersion = version;
|
||||
myVersionComparator = versionComparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in 2018.2.
|
||||
* @return concatenated representation of relation and version so it could be easily parsed or displayed.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public PyRequirementVersionSpec withVersionComparator(@NotNull Comparator<String> comparator) {
|
||||
return new PyRequirementVersionSpec(myRelation, myParsedVersion, myVersion, comparator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myRelation + myVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
final PyRequirementVersionSpec spec = (PyRequirementVersionSpec)o;
|
||||
return myRelation == spec.myRelation && myVersion.equals(spec.myVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * myRelation.hashCode() + myVersion.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyRequirementRelation getRelation() {
|
||||
return myRelation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getVersion() {
|
||||
return myVersion;
|
||||
}
|
||||
|
||||
public boolean matches(@NotNull String version) {
|
||||
switch (myRelation) {
|
||||
case LT:
|
||||
return myVersionComparator.compare(version, myVersion) < 0;
|
||||
case LTE:
|
||||
return myVersionComparator.compare(version, myVersion) <= 0;
|
||||
case GT:
|
||||
return myVersionComparator.compare(version, myVersion) > 0;
|
||||
case GTE:
|
||||
return myVersionComparator.compare(version, myVersion) >= 0;
|
||||
case EQ:
|
||||
Objects.requireNonNull(myParsedVersion);
|
||||
|
||||
final Pair<String, String> publicAndLocalVersions = splitIntoPublicAndLocalVersions(myParsedVersion);
|
||||
final Pair<String, String> otherPublicAndLocalVersions = splitIntoPublicAndLocalVersions(version);
|
||||
final boolean publicVersionsAreSame =
|
||||
myVersionComparator.compare(otherPublicAndLocalVersions.first, publicAndLocalVersions.first) == 0;
|
||||
|
||||
return publicVersionsAreSame &&
|
||||
(publicAndLocalVersions.second.isEmpty() || otherPublicAndLocalVersions.second.equals(publicAndLocalVersions.second));
|
||||
case NE:
|
||||
return myVersionComparator.compare(version, myVersion) != 0;
|
||||
case COMPATIBLE:
|
||||
Objects.requireNonNull(myParsedVersion);
|
||||
|
||||
return new PyRequirementVersionSpec(PyRequirementRelation.GTE, myParsedVersion)
|
||||
.withVersionComparator(myVersionComparator)
|
||||
.matches(version) &&
|
||||
new PyRequirementVersionSpec(PyRequirementRelation.EQ, toEqPartOfCompatibleRelation(myParsedVersion))
|
||||
.withVersionComparator(myVersionComparator)
|
||||
.matches(version);
|
||||
case STR_EQ:
|
||||
return version.equals(myVersion);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<String, String> splitIntoPublicAndLocalVersions(@NotNull PyRequirementVersion version) {
|
||||
final PyRequirementVersion withoutLocal =
|
||||
new PyRequirementVersion(version.getEpoch(), version.getRelease(), version.getPre(), version.getPost(), version.getDev(), null);
|
||||
|
||||
return Pair.createNonNull(withoutLocal.getPresentableText(), StringUtil.notNullize(version.getLocal()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<String, String> splitIntoPublicAndLocalVersions(@NotNull String version) {
|
||||
final String[] publicAndLocalVersions = version.split("\\+", 2);
|
||||
|
||||
final String publicVersion = publicAndLocalVersions[0];
|
||||
final String localVersion = publicAndLocalVersions.length == 1 ? "" : publicAndLocalVersions[1];
|
||||
|
||||
return Pair.createNonNull(publicVersion, localVersion);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyRequirementVersion toEqPartOfCompatibleRelation(@NotNull PyRequirementVersion version) {
|
||||
final String release = version.getRelease();
|
||||
final int lastPoint = release.lastIndexOf('.');
|
||||
|
||||
if (lastPoint == -1) return version;
|
||||
|
||||
return new PyRequirementVersion(version.getEpoch(), release.substring(0, lastPoint + 1) + "*", null, null, null, null);
|
||||
default String getPresentableText() {
|
||||
return getRelation().getPresentableText() + getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ import com.jetbrains.python.documentation.docstrings.DocStringFormat;
|
||||
import com.jetbrains.python.packaging.PyPackageManagerUI;
|
||||
import com.jetbrains.python.packaging.PyPackageRequirementsSettings;
|
||||
import com.jetbrains.python.packaging.PyPackageUtil;
|
||||
import com.jetbrains.python.packaging.PyRequirement;
|
||||
import com.jetbrains.python.packaging.PyRequirementsKt;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.testing.PyTestFrameworkService;
|
||||
@@ -164,7 +164,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable {
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.install(Collections.singletonList(new PyRequirement(name)), Collections.emptyList());
|
||||
ui.install(Collections.singletonList(PyRequirementsKt.pyRequirement(name)), Collections.emptyList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
|
||||
plural ? "are" : "is");
|
||||
final Set<String> unsatisfiedNames = new HashSet<>();
|
||||
for (PyRequirement req : unsatisfied) {
|
||||
unsatisfiedNames.add(req.getFullName());
|
||||
unsatisfiedNames.add(req.getName() + req.getExtras());
|
||||
}
|
||||
final List<LocalQuickFix> quickFixes = new ArrayList<>();
|
||||
quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied));
|
||||
@@ -484,7 +484,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.install(Collections.singletonList(new PyRequirement(myPackageName)), Collections.emptyList());
|
||||
ui.install(Collections.singletonList(PyRequirementsKt.pyRequirement(myPackageName)), Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -46,6 +46,7 @@ import com.jetbrains.python.inspections.quickfix.*;
|
||||
import com.jetbrains.python.packaging.PyPIPackageUtil;
|
||||
import com.jetbrains.python.packaging.PyPackageUtil;
|
||||
import com.jetbrains.python.packaging.PyRequirement;
|
||||
import com.jetbrains.python.packaging.PyRequirementsKt;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.impl.PyImportStatementNavigator;
|
||||
@@ -683,7 +684,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
|
||||
}
|
||||
|
||||
private static void addInstallPackageAction(List<LocalQuickFix> actions, String packageName, Module module, Sdk sdk) {
|
||||
final List<PyRequirement> requirements = Collections.singletonList(new PyRequirement(packageName));
|
||||
final List<PyRequirement> requirements = Collections.singletonList(PyRequirementsKt.pyRequirement(packageName));
|
||||
final String name = "Install package " + packageName;
|
||||
actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements));
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class PyCondaPackageManagerImpl extends PyPackageManagerImpl {
|
||||
if (useConda) {
|
||||
final ArrayList<String> arguments = new ArrayList<>();
|
||||
for (PyRequirement requirement : requirements) {
|
||||
arguments.add(requirement.toString());
|
||||
arguments.add(requirement.getPresentableText());
|
||||
}
|
||||
arguments.add("-y");
|
||||
if (!extraArgs.contains("-U")) {
|
||||
@@ -154,7 +154,7 @@ public class PyCondaPackageManagerImpl extends PyPackageManagerImpl {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected static List<PyPackage> parseCondaToolOutput(@NotNull String s) throws ExecutionException {
|
||||
private List<PyPackage> parseCondaToolOutput(@NotNull String s) throws ExecutionException {
|
||||
final String[] lines = StringUtil.splitByLines(s);
|
||||
final List<PyPackage> packages = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
@@ -169,7 +169,7 @@ public class PyCondaPackageManagerImpl extends PyPackageManagerImpl {
|
||||
if (fields.size() >= 4) {
|
||||
final String requiresLine = fields.get(3);
|
||||
final String requiresSpec = StringUtil.join(StringUtil.split(requiresLine, ":"), "\n");
|
||||
requirements.addAll(PyPackageUtil.fix(PyRequirement.fromText(requiresSpec)));
|
||||
requirements.addAll(parseRequirements(requiresSpec));
|
||||
}
|
||||
if (!"Python".equals(name)) {
|
||||
packages.add(new PyPackage(name, version, "", requirements));
|
||||
|
||||
@@ -174,7 +174,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
|
||||
|
||||
@Override
|
||||
public void install(@NotNull String requirementString) throws ExecutionException {
|
||||
install(Collections.singletonList(PyRequirement.fromLine(requirementString)), Collections.emptyList());
|
||||
install(Collections.singletonList(parseRequirement(requirementString)), Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -391,10 +391,22 @@ public class PyPackageManagerImpl extends PyPackageManager {
|
||||
.orElseGet(() -> PyPackageUtil.findSetupPyRequires(module));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyRequirement parseRequirement(@NotNull String line) {
|
||||
return PyRequirementParser.fromLine(line);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyRequirement> parseRequirements(@NotNull String text) {
|
||||
return PyPackageUtil.fix(PyRequirement.fromText(text));
|
||||
return PyRequirementParser.fromText(text);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyRequirement> parseRequirements(@NotNull VirtualFile file) {
|
||||
return PyRequirementParser.fromFile(file);
|
||||
}
|
||||
|
||||
// public List<PyPackage> refreshAndGetPackagesIfNotInProgress(boolean alwaysRefresh) throws ExecutionException
|
||||
@@ -547,7 +559,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyPackage> parsePackagingToolOutput(@NotNull String s) throws ExecutionException {
|
||||
private List<PyPackage> parsePackagingToolOutput(@NotNull String s) throws ExecutionException {
|
||||
final String[] lines = StringUtil.splitByLines(s);
|
||||
final List<PyPackage> packages = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
@@ -562,7 +574,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
|
||||
if (fields.size() >= 4) {
|
||||
final String requiresLine = fields.get(3);
|
||||
final String requiresSpec = StringUtil.join(StringUtil.split(requiresLine, ":"), "\n");
|
||||
requirements.addAll(PyPackageUtil.fix(PyRequirement.fromText(requiresSpec)));
|
||||
requirements.addAll(parseRequirements(requiresSpec));
|
||||
}
|
||||
if (!"Python".equals(name)) {
|
||||
packages.add(new PyPackage(name, version, location, requirements));
|
||||
|
||||
@@ -37,7 +37,6 @@ import com.intellij.openapi.vfs.VirtualFileVisitor;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
@@ -49,6 +48,7 @@ import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.remote.PyCredentialsContribution;
|
||||
import com.jetbrains.python.sdk.CredentialsTypeExChecker;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -125,30 +125,24 @@ public class PyPackageUtil {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyListLiteralExpression findSetupPyInstallRequires(@NotNull Module module, @Nullable PyCallExpression setupCall) {
|
||||
if (setupCall == null) {
|
||||
return null;
|
||||
}
|
||||
private static PyListLiteralExpression findSetupPyInstallRequires(@Nullable PyCallExpression setupCall) {
|
||||
if (setupCall == null) return null;
|
||||
|
||||
return Stream
|
||||
return StreamEx
|
||||
.of(REQUIRES, INSTALL_REQUIRES)
|
||||
.map(setupCall::getKeywordArgument)
|
||||
.map(requires -> resolveValue(module, requires, PyListLiteralExpression.class))
|
||||
.filter(requires -> requires != null)
|
||||
.findFirst()
|
||||
.map(requires -> resolveValue(requires, PyListLiteralExpression.class))
|
||||
.findFirst(Objects::nonNull)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<PyRequirement> findSetupPyRequires(@NotNull Module module) {
|
||||
final PyCallExpression setupCall = findSetupCall(module);
|
||||
if (setupCall == null) return null;
|
||||
|
||||
if (setupCall == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<PyRequirement> requirementsFromRequires = getSetupPyRequiresFromArguments(module, setupCall, SETUP_PY_REQUIRES_KWARGS_NAMES);
|
||||
final List<PyRequirement> requirementsFromLinks = getSetupPyRequiresFromArguments(module, setupCall, DEPENDENCY_LINKS);
|
||||
final List<PyRequirement> requirementsFromRequires = getSetupPyRequiresFromArguments(setupCall, SETUP_PY_REQUIRES_KWARGS_NAMES);
|
||||
final List<PyRequirement> requirementsFromLinks = getSetupPyRequiresFromArguments(setupCall, DEPENDENCY_LINKS);
|
||||
|
||||
return mergeSetupPyRequirements(requirementsFromRequires, requirementsFromLinks);
|
||||
}
|
||||
@@ -159,7 +153,7 @@ public class PyPackageUtil {
|
||||
if (setupCall == null) return null;
|
||||
|
||||
final PyDictLiteralExpression extrasRequire =
|
||||
resolveValue(module, setupCall.getKeywordArgument("extras_require"), PyDictLiteralExpression.class);
|
||||
resolveValue(setupCall.getKeywordArgument("extras_require"), PyDictLiteralExpression.class);
|
||||
if (extrasRequire == null) return null;
|
||||
|
||||
final Map<String, List<PyRequirement>> result = new HashMap<>();
|
||||
@@ -191,8 +185,7 @@ public class PyPackageUtil {
|
||||
}
|
||||
|
||||
if (requiresValue != null) {
|
||||
return Pair.createNonNull(((PyStringLiteralExpression)extra).getStringValue(),
|
||||
fix(PyRequirement.fromText((requiresValue))));
|
||||
return Pair.createNonNull(((PyStringLiteralExpression)extra).getStringValue(), PyRequirementParser.fromText(requiresValue));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,42 +193,21 @@ public class PyPackageUtil {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> getSetupPyRequiresFromArguments(@NotNull Module module,
|
||||
@NotNull PyCallExpression setupCall,
|
||||
private static List<PyRequirement> getSetupPyRequiresFromArguments(@NotNull PyCallExpression setupCall,
|
||||
@NotNull String... argumentNames) {
|
||||
return fix(
|
||||
PyRequirement.fromText(
|
||||
Stream
|
||||
.of(argumentNames)
|
||||
.map(setupCall::getKeywordArgument)
|
||||
.map(requires -> resolveValue(module, requires, PyListLiteralExpression.class))
|
||||
.filter(requires -> requires != null)
|
||||
.flatMap(requires -> Stream.of(requires.getElements()))
|
||||
.filter(PyStringLiteralExpression.class::isInstance)
|
||||
.map(requirement -> ((PyStringLiteralExpression)requirement).getStringValue())
|
||||
.collect(Collectors.joining("\n"))
|
||||
)
|
||||
return PyRequirementParser.fromText(
|
||||
StreamEx
|
||||
.of(argumentNames)
|
||||
.map(setupCall::getKeywordArgument)
|
||||
.map(requires -> resolveValue(requires, PyListLiteralExpression.class))
|
||||
.nonNull()
|
||||
.flatMap(requires -> Stream.of(requires.getElements()))
|
||||
.select(PyStringLiteralExpression.class)
|
||||
.map(StringLiteralExpression::getStringValue)
|
||||
.joining("\n")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in 2018.2.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public static PyRequirement fix(@NotNull PyRequirement requirement) {
|
||||
return requirement.withVersionComparator(PyPackageVersionComparator.getSTR_COMPARATOR());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in 2018.2.
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
public static List<PyRequirement> fix(@NotNull List<PyRequirement> requirements) {
|
||||
return ContainerUtil.map(requirements, PyPackageUtil::fix);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> mergeSetupPyRequirements(@NotNull List<PyRequirement> requirementsFromRequires,
|
||||
@NotNull List<PyRequirement> requirementsFromLinks) {
|
||||
@@ -254,12 +226,12 @@ public class PyPackageUtil {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T extends PyExpression> T resolveValue(@NotNull Module module, @Nullable PyExpression expression, @NotNull Class<T> cls) {
|
||||
private static <T extends PyExpression> T resolveValue(@Nullable PyExpression expression, @NotNull Class<T> cls) {
|
||||
if (cls.isInstance(expression)) {
|
||||
return cls.cast(expression);
|
||||
}
|
||||
if (expression instanceof PyReferenceExpression) {
|
||||
final TypeEvalContext context = TypeEvalContext.deepCodeInsight(module.getProject());
|
||||
final TypeEvalContext context = TypeEvalContext.deepCodeInsight(expression.getProject());
|
||||
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
||||
final QualifiedResolveResult result = ((PyReferenceExpression)expression).followAssignmentsChain(resolveContext);
|
||||
final PsiElement element = result.getElement();
|
||||
@@ -287,7 +259,7 @@ public class PyPackageUtil {
|
||||
|
||||
@NotNull
|
||||
public static String requirementsToString(@NotNull List<PyRequirement> requirements) {
|
||||
return StringUtil.join(requirements, requirement -> String.format("'%s'", requirement.toString()), ", ");
|
||||
return StringUtil.join(requirements, requirement -> String.format("'%s'", requirement.getPresentableText()), ", ");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -431,7 +403,7 @@ public class PyPackageUtil {
|
||||
public static List<PyRequirement> getRequirementsFromTxt(@NotNull Module module) {
|
||||
final VirtualFile requirementsTxt = findRequirementsTxt(module);
|
||||
if (requirementsTxt != null) {
|
||||
return fix(PyRequirement.fromFile(requirementsTxt));
|
||||
return PyRequirementParser.fromFile(requirementsTxt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -454,7 +426,7 @@ public class PyPackageUtil {
|
||||
}
|
||||
|
||||
final PyCallExpression setupCall = findSetupCall(setupPy);
|
||||
final PyListLiteralExpression installRequires = findSetupPyInstallRequires(module, setupCall);
|
||||
final PyListLiteralExpression installRequires = findSetupPyInstallRequires(setupCall);
|
||||
final PyElementGenerator generator = PyElementGenerator.getInstance(module.getProject());
|
||||
|
||||
if (installRequires != null && installRequires.isWritable()) {
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging
|
||||
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersion
|
||||
|
||||
/**
|
||||
* Presents normalized version of python package or requirement as described [here][https://www.python.org/dev/peps/pep-0440/#normalization].
|
||||
* Presents normalized version of python package or requirement as described in [PEP-440][https://www.python.org/dev/peps/pep-0440/#normalization].
|
||||
*
|
||||
* Instances of this class MUST be converted from [com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalize] result.
|
||||
* Instances of this class MUST be obtained from [PyPackageVersionNormalizer.normalize].
|
||||
*/
|
||||
data class PyPackageVersion(val epoch: String? = null,
|
||||
val release: String,
|
||||
val pre: String? = null,
|
||||
val post: String? = null,
|
||||
val dev: String? = null,
|
||||
val local: String? = null) {
|
||||
data class PyPackageVersion internal constructor(val epoch: String? = null,
|
||||
val release: String,
|
||||
val pre: String? = null,
|
||||
val post: String? = null,
|
||||
val dev: String? = null,
|
||||
val local: String? = null) {
|
||||
|
||||
override fun toString(): String = PyRequirementVersion(epoch, release, pre, post, dev, local).presentableText
|
||||
/**
|
||||
* String representation that follows spelling described in [PEP-440][https://www.python.org/dev/peps/pep-0440/#normalization]
|
||||
*/
|
||||
val presentableText
|
||||
get() =
|
||||
sequenceOf(epochPresentable(), release, pre, postPresentable(), devPresentable(), localPresentable())
|
||||
.filterNotNull()
|
||||
.joinToString(separator = "") { it }
|
||||
|
||||
private fun epochPresentable() = epoch?.let { "$it!" }
|
||||
private fun postPresentable() = post?.let { ".$it" }
|
||||
private fun devPresentable() = dev?.let { ".$it" }
|
||||
private fun localPresentable() = local?.let { "+$it" }
|
||||
}
|
||||
@@ -3,26 +3,27 @@ package com.jetbrains.python.packaging
|
||||
|
||||
import com.intellij.webcore.packaging.PackageVersionComparator
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersion
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer
|
||||
import one.util.streamex.EntryStream
|
||||
import one.util.streamex.StreamEx
|
||||
import java.math.BigInteger
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* Compares normalized [PyPackageVersions][PyPackageVersion].
|
||||
* Compares [PyPackageVersions][PyPackageVersion].
|
||||
*
|
||||
* Based on [PEP-440][https://www.python.org/dev/peps/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering].
|
||||
*/
|
||||
object PyPackageVersionComparator : Comparator<PyPackageVersion> {
|
||||
|
||||
/**
|
||||
* Compares versions by normalizing them or using [com.intellij.webcore.packaging.PackageVersionComparator.VERSION_COMPARATOR] as a fallback
|
||||
*/
|
||||
@JvmStatic
|
||||
val STR_COMPARATOR: Comparator<String> = Comparator { o1, o2 ->
|
||||
val normalized1 = PyRequirementVersionNormalizer.normalize(o1)?.toPkgVersion()
|
||||
val normalized1 = PyPackageVersionNormalizer.normalize(o1)
|
||||
?: return@Comparator PackageVersionComparator.VERSION_COMPARATOR.compare(o1, o2)
|
||||
|
||||
val normalized2 = PyRequirementVersionNormalizer.normalize(o2)?.toPkgVersion()
|
||||
val normalized2 = PyPackageVersionNormalizer.normalize(o2)
|
||||
?: return@Comparator PackageVersionComparator.VERSION_COMPARATOR.compare(o1, o2)
|
||||
|
||||
compare(normalized1, normalized2)
|
||||
@@ -51,16 +52,14 @@ object PyPackageVersionComparator : Comparator<PyPackageVersion> {
|
||||
return compareLocals(o1, o2)
|
||||
}
|
||||
|
||||
private fun PyRequirementVersion.toPkgVersion() = PyPackageVersion(epoch, release, pre, post, dev, local)
|
||||
|
||||
/**
|
||||
* @see com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalizeEpoch
|
||||
* @see PyPackageVersionNormalizer.normalizeEpoch
|
||||
*/
|
||||
private fun compareEpochs(o1: PyPackageVersion, o2: PyPackageVersion) = compareAsInts(
|
||||
o1.epoch ?: "0", o2.epoch ?: "0")
|
||||
|
||||
/**
|
||||
* @see com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalizeRelease
|
||||
* @see PyPackageVersionNormalizer.normalizeRelease
|
||||
*/
|
||||
private fun compareReleases(o1: PyPackageVersion, o2: PyPackageVersion): PyRequirementRelation {
|
||||
for ((releasePart1, releasePart2) in zipLongest(o1.release.split('.'),
|
||||
@@ -88,7 +87,7 @@ object PyPackageVersionComparator : Comparator<PyPackageVersion> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalizePost
|
||||
* @see PyPackageVersionNormalizer.normalizePost
|
||||
*/
|
||||
private fun comparePosts(o1: PyPackageVersion, o2: PyPackageVersion): Int {
|
||||
return compareAsInts(o1.post?.substring(4) ?: "-1",
|
||||
@@ -96,7 +95,7 @@ object PyPackageVersionComparator : Comparator<PyPackageVersion> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalizePre
|
||||
* @see PyPackageVersionNormalizer.normalizePre
|
||||
*/
|
||||
private fun comparePres(o1: PyPackageVersion, o2: PyPackageVersion): Int {
|
||||
val pre1 = o1.pre
|
||||
@@ -116,7 +115,7 @@ object PyPackageVersionComparator : Comparator<PyPackageVersion> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalizeDev
|
||||
* @see PyPackageVersionNormalizer.normalizeDev
|
||||
*/
|
||||
private fun compareDevs(o1: PyPackageVersion, o2: PyPackageVersion): Int {
|
||||
val dev1 = o1.dev
|
||||
@@ -130,7 +129,7 @@ object PyPackageVersionComparator : Comparator<PyPackageVersion> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer.normalizeLocal
|
||||
* @see PyPackageVersionNormalizer.normalizeLocal
|
||||
*/
|
||||
private fun compareLocals(o1: PyPackageVersion, o2: PyPackageVersion) = (o1.local ?: "").compareTo(o2.local ?: "")
|
||||
|
||||
|
||||
+5
-21
@@ -1,17 +1,5 @@
|
||||
// Copyright 2000-2017 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.requirement;
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -29,12 +17,8 @@ import java.util.regex.Pattern;
|
||||
* <a href="https://www.python.org/dev/peps/pep-0440/#normalization">https://www.python.org/dev/peps/pep-0440/#normalization</a>
|
||||
* and
|
||||
* <a href="https://www.python.org/dev/peps/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering">https://www.python.org/dev/peps/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering</a>.
|
||||
*
|
||||
* @deprecated Use {@link com.jetbrains.python.packaging.PyRequirement} instead.
|
||||
* This class will be removed in 2018.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class PyRequirementVersionNormalizer {
|
||||
public final class PyPackageVersionNormalizer {
|
||||
|
||||
@NotNull
|
||||
private static final String EPOCH_GROUP = "epoch";
|
||||
@@ -114,10 +98,10 @@ public final class PyRequirementVersionNormalizer {
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@Nullable
|
||||
public static PyRequirementVersion normalize(@NotNull String version) {
|
||||
public static PyPackageVersion normalize(@NotNull String version) {
|
||||
final Matcher matcher = VERSION.matcher(version);
|
||||
if (matcher.matches()) {
|
||||
return new PyRequirementVersion(
|
||||
return new PyPackageVersion(
|
||||
normalizeEpoch(matcher),
|
||||
normalizeRelease(matcher),
|
||||
normalizePre(matcher),
|
||||
@@ -7,8 +7,6 @@ import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersion;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -47,7 +45,7 @@ public class PyPackagingSettings implements PersistentStateComponent<PyPackaging
|
||||
.of(versions)
|
||||
.findFirst(
|
||||
version -> {
|
||||
final PyRequirementVersion normalized = PyRequirementVersionNormalizer.normalize(version);
|
||||
final PyPackageVersion normalized = PyPackageVersionNormalizer.normalize(version);
|
||||
|
||||
return normalized == null || normalized.getPre() == null && normalized.getDev() == null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging
|
||||
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec
|
||||
|
||||
/**
|
||||
* This class is not an API, consider using methods listed below.
|
||||
*
|
||||
* @see PyPackageManager.parseRequirement
|
||||
* @see PyPackageManager.parseRequirements
|
||||
*
|
||||
* @see PyRequirementParser.fromText
|
||||
* @see PyRequirementParser.fromLine
|
||||
* @see PyRequirementParser.fromFile
|
||||
*/
|
||||
data class PyRequirementImpl(private val name: String,
|
||||
private val versionSpecs: List<PyRequirementVersionSpec>,
|
||||
private val installOptions: List<String>,
|
||||
private val extras: String) : PyRequirement {
|
||||
|
||||
override fun getName() = name
|
||||
override fun getExtras() = extras
|
||||
override fun getVersionSpecs() = versionSpecs
|
||||
override fun getInstallOptions() = installOptions
|
||||
|
||||
override fun match(packages: Collection<PyPackage>): PyPackage? {
|
||||
val normalizedName = name.replace('_', '-')
|
||||
return packages.firstOrNull { normalizedName.equals(it.name, true) && versionSpecs.all { spec -> spec.matches(it.version) } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* @see <a href="https://pip.pypa.io/en/stable/reference/pip_install/"><code>pip install</code> documentation</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0508/">PEP-508</a>
|
||||
* @see <a href="https://www.python.org/dev/peps/pep-0440/">PEP-440</a>
|
||||
* @see PyRequirement
|
||||
* @see PyPackageVersionNormalizer
|
||||
* @see PyPackageManager#parseRequirement(String)
|
||||
* @see PyPackageManager#parseRequirements(String)
|
||||
* @see PyPackageManager#parseRequirements(VirtualFile)
|
||||
*/
|
||||
public class PyRequirementParser {
|
||||
|
||||
// common regular expressions
|
||||
|
||||
@NotNull
|
||||
private static final String LINE_WS_REGEXP = "[ \t]";
|
||||
|
||||
@NotNull
|
||||
private static final String COMMENT_GROUP = "comment";
|
||||
|
||||
@NotNull
|
||||
private static final String COMMENT_REGEXP = "(?<" + COMMENT_GROUP + ">" + LINE_WS_REGEXP + "+#.*)?";
|
||||
|
||||
@NotNull
|
||||
private static final String NAME_GROUP = "name";
|
||||
|
||||
// PEP-508
|
||||
// https://www.python.org/dev/peps/pep-0508/
|
||||
|
||||
@NotNull
|
||||
private static final String IDENTIFIER_REGEXP = "[A-Za-z0-9]([-_\\.]?[A-Za-z0-9])*";
|
||||
|
||||
@NotNull
|
||||
private static final String NAME_REGEXP = "(?<" + NAME_GROUP + ">" + IDENTIFIER_REGEXP + ")";
|
||||
|
||||
@NotNull
|
||||
private static final String EXTRAS_REGEXP =
|
||||
"\\[" + IDENTIFIER_REGEXP + "(" + LINE_WS_REGEXP + "*," + LINE_WS_REGEXP + "*" + IDENTIFIER_REGEXP + ")*" + "\\]";
|
||||
|
||||
// archive-related regular expressions
|
||||
|
||||
@NotNull
|
||||
private static final Pattern GITHUB_ARCHIVE_URL =
|
||||
Pattern.compile("https?://github\\.com/[^/\\s]+/(?<" + NAME_GROUP + ">[^/\\s]+)/archive/\\S+" + COMMENT_REGEXP);
|
||||
|
||||
@NotNull
|
||||
private static final Pattern GITLAB_ARCHIVE_URL =
|
||||
Pattern.compile("https?://gitlab\\.com/[^/\\s]+/(?<" + NAME_GROUP + ">[^/\\s]+)/repository/\\S+" + COMMENT_REGEXP);
|
||||
|
||||
@NotNull
|
||||
private static final Pattern ARCHIVE_URL =
|
||||
Pattern.compile("https?://\\S+/" +
|
||||
"(?<" + NAME_GROUP + ">\\S+)" +
|
||||
"(\\.tar\\.gz|\\.zip)(#(sha1|sha224|sha256|sha384|sha512|md5)=\\w+)?" + COMMENT_REGEXP);
|
||||
|
||||
// vcs-related regular expressions
|
||||
// don't forget to update calculateVcsInstallOptions(Matcher) after this section changing
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EDITABLE_GROUP = "editable";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EDITABLE_REGEXP = "((?<" + VCS_EDITABLE_GROUP + ">-e|--editable)" + LINE_WS_REGEXP + "+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_BEFORE_GROUP = "srcb";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_AFTER_GROUP = "srca";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_BEFORE_REGEXP =
|
||||
"(?<" + VCS_SRC_BEFORE_GROUP + ">--src" + LINE_WS_REGEXP + "+\\S+" + LINE_WS_REGEXP + "+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_SRC_AFTER_REGEXP =
|
||||
"(?<" + VCS_SRC_AFTER_GROUP + ">" + LINE_WS_REGEXP + "+--src" + LINE_WS_REGEXP + "+\\S+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String PATH_IN_VCS_GROUP = "path";
|
||||
|
||||
@NotNull
|
||||
private static final String PATH_IN_VCS_REGEXP = "(?<" + PATH_IN_VCS_GROUP + ">[^@#\\s]+)";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_REVISION_REGEXP = "(@[^#\\s]+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EGG_BEFORE_SUBDIR_GROUP = "eggb";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EGG_AFTER_SUBDIR_GROUP = "egga";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EXTRAS_BEFORE_SUBDIR_GROUP = "extrasb";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_EXTRAS_AFTER_SUBDIR_GROUP = "extrasa";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_PARAMS_REGEXP =
|
||||
"(" +
|
||||
"(" +
|
||||
"#egg=(?<" + VCS_EGG_BEFORE_SUBDIR_GROUP + ">[^&\\s\\[\\]]+)(?<" + VCS_EXTRAS_BEFORE_SUBDIR_GROUP + ">" + EXTRAS_REGEXP + ")?" +
|
||||
"(&subdirectory=\\S+)?" +
|
||||
")" +
|
||||
"|" +
|
||||
"(" +
|
||||
"#subdirectory=[^&\\s]+" +
|
||||
"&egg=(?<" + VCS_EGG_AFTER_SUBDIR_GROUP + ">[^\\s\\[\\]]+)(?<" + VCS_EXTRAS_AFTER_SUBDIR_GROUP + ">" + EXTRAS_REGEXP + ")?" +
|
||||
")" +
|
||||
")?";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_GROUP = "vcs";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_URL_PREFIX = VCS_SRC_BEFORE_REGEXP + VCS_EDITABLE_REGEXP + "(?<" + VCS_GROUP + ">";
|
||||
|
||||
@NotNull
|
||||
private static final String VCS_URL_SUFFIX =
|
||||
PATH_IN_VCS_REGEXP + VCS_REVISION_REGEXP + VCS_PARAMS_REGEXP + ")" + VCS_SRC_AFTER_REGEXP + COMMENT_REGEXP;
|
||||
|
||||
@NotNull
|
||||
private static final String GIT_USER_AT_REGEXP = "[\\w-]+@";
|
||||
|
||||
// supports: git+user@...
|
||||
@NotNull
|
||||
private static final Pattern GIT_PROJECT_URL =
|
||||
Pattern.compile(VCS_URL_PREFIX + "git\\+" + GIT_USER_AT_REGEXP + "[^:\\s]+:" + VCS_URL_SUFFIX);
|
||||
|
||||
// supports: bzr+lp:...
|
||||
@NotNull
|
||||
private static final Pattern BZR_PROJECT_URL = Pattern.compile(VCS_URL_PREFIX + "bzr\\+lp:" + VCS_URL_SUFFIX);
|
||||
|
||||
// supports: (bzr|git|hg|svn)(+smth)?://...
|
||||
@NotNull
|
||||
private static final Pattern VCS_PROJECT_URL =
|
||||
Pattern.compile(VCS_URL_PREFIX + "(bzr|git|hg|svn)(\\+[A-Za-z]+)?://?[^/]+/" + VCS_URL_SUFFIX);
|
||||
|
||||
// requirement-related regular expressions
|
||||
// don't forget to update calculateRequirementInstallOptions(Matcher) after this section changing
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_EXTRAS_GROUP = "extras";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_EXTRAS_REGEXP = "(?<" + REQUIREMENT_EXTRAS_GROUP + ">" + EXTRAS_REGEXP + ")?";
|
||||
|
||||
// PEP-440
|
||||
// https://www.python.org/dev/peps/pep-0440/
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_VERSIONS_SPECS_GROUP = "versionspecs";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_VERSION_SPEC_REGEXP = "(<=?|!=|===?|>=?|~=)" + LINE_WS_REGEXP + "*[\\.\\*\\+!\\w-]+";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_VERSIONS_SPECS_REGEXP =
|
||||
"(?<" + REQUIREMENT_VERSIONS_SPECS_GROUP + ">" + REQUIREMENT_VERSION_SPEC_REGEXP +
|
||||
"(" + LINE_WS_REGEXP + "*," + LINE_WS_REGEXP + "*" + REQUIREMENT_VERSION_SPEC_REGEXP + ")*)?";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_OPTIONS_GROUP = "options";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_OPTIONS_REGEXP =
|
||||
"(?<" + REQUIREMENT_OPTIONS_GROUP + ">(" + LINE_WS_REGEXP + "+(--global-option|--install-option)=\"[^\"]*\")+)?";
|
||||
|
||||
@NotNull
|
||||
private static final String REQUIREMENT_GROUP = "requirement";
|
||||
|
||||
@NotNull
|
||||
private static final Pattern REQUIREMENT = Pattern.compile(
|
||||
"(?<" + REQUIREMENT_GROUP + ">" +
|
||||
NAME_REGEXP +
|
||||
LINE_WS_REGEXP + "*" +
|
||||
REQUIREMENT_EXTRAS_REGEXP +
|
||||
LINE_WS_REGEXP + "*" +
|
||||
REQUIREMENT_VERSIONS_SPECS_REGEXP +
|
||||
")" +
|
||||
REQUIREMENT_OPTIONS_REGEXP +
|
||||
COMMENT_REGEXP);
|
||||
|
||||
@Nullable
|
||||
public static PyRequirement fromLine(@NotNull String line) {
|
||||
final PyRequirement githubArchiveUrl = parseGitArchiveUrl(GITHUB_ARCHIVE_URL, line);
|
||||
if (githubArchiveUrl != null) {
|
||||
return githubArchiveUrl;
|
||||
}
|
||||
|
||||
final PyRequirement gitlabArchiveUrl = parseGitArchiveUrl(GITLAB_ARCHIVE_URL, line);
|
||||
if (gitlabArchiveUrl != null) {
|
||||
return gitlabArchiveUrl;
|
||||
}
|
||||
|
||||
final PyRequirement archiveUrl = parseArchiveUrl(line);
|
||||
if (archiveUrl != null) {
|
||||
return archiveUrl;
|
||||
}
|
||||
|
||||
final PyRequirement vcsProjectUrl = parseVcsProjectUrl(line);
|
||||
if (vcsProjectUrl != null) {
|
||||
return vcsProjectUrl;
|
||||
}
|
||||
|
||||
return parseRequirement(line);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PyRequirement> fromText(@NotNull String text) {
|
||||
return fromText(text, null, new HashSet<>());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PyRequirement> fromFile(@NotNull VirtualFile file) {
|
||||
return fromText(loadText(file), file, new HashSet<>());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseGitArchiveUrl(@NotNull Pattern pattern, @NotNull String line) {
|
||||
final Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return new PyRequirementImpl(matcher.group(NAME_GROUP), Collections.emptyList(),
|
||||
Collections.singletonList(dropComments(line, matcher)), "");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseArchiveUrl(@NotNull String line) {
|
||||
final Matcher matcher = ARCHIVE_URL.matcher(line);
|
||||
|
||||
if (matcher.matches()) {
|
||||
return createVcsOrArchiveRequirement(parseNameAndVersionFromVcsOrArchive(matcher.group(NAME_GROUP)),
|
||||
Collections.singletonList(dropComments(line, matcher)),
|
||||
null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseVcsProjectUrl(@NotNull String line) {
|
||||
final Matcher vcsMatcher = VCS_PROJECT_URL.matcher(line);
|
||||
if (vcsMatcher.matches()) {
|
||||
return createVcsRequirement(vcsMatcher);
|
||||
}
|
||||
|
||||
final Matcher gitMatcher = GIT_PROJECT_URL.matcher(line);
|
||||
if (gitMatcher.matches()) {
|
||||
return createVcsRequirement(gitMatcher);
|
||||
}
|
||||
|
||||
final Matcher bzrMatcher = BZR_PROJECT_URL.matcher(line);
|
||||
if (bzrMatcher.matches()) {
|
||||
return createVcsRequirement(bzrMatcher);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirement parseRequirement(@NotNull String line) {
|
||||
final Matcher matcher = REQUIREMENT.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
final String name = matcher.group(NAME_GROUP);
|
||||
final List<PyRequirementVersionSpec> versionSpecs = parseVersionSpecs(matcher.group(REQUIREMENT_VERSIONS_SPECS_GROUP));
|
||||
final List<String> installOptions = calculateRequirementInstallOptions(matcher);
|
||||
final String extras = matcher.group(REQUIREMENT_EXTRAS_GROUP);
|
||||
|
||||
if (extras == null) {
|
||||
return new PyRequirementImpl(name, versionSpecs, installOptions, "");
|
||||
}
|
||||
else {
|
||||
return new PyRequirementImpl(name, versionSpecs, installOptions, extras);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> fromText(@NotNull String text,
|
||||
@Nullable VirtualFile containingFile,
|
||||
@NotNull Set<VirtualFile> visitedFiles) {
|
||||
if (containingFile != null) {
|
||||
visitedFiles.add(containingFile);
|
||||
}
|
||||
|
||||
return StreamEx
|
||||
.of(splitByLinesAndCollapse(text))
|
||||
.flatCollection(line -> parseLine(line, containingFile, visitedFiles))
|
||||
.nonNull()
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String loadText(@NotNull VirtualFile file) {
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(file);
|
||||
|
||||
return document == null ? "" : document.getText();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String dropComments(@NotNull String line, @NotNull Matcher matcher) {
|
||||
final int commentIndex = matcher.start(COMMENT_GROUP);
|
||||
|
||||
if (commentIndex == -1) {
|
||||
return line;
|
||||
}
|
||||
|
||||
return line.substring(0, findFirstNotWhiteSpaceBefore(line, commentIndex) + 1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<String, String> parseNameAndVersionFromVcsOrArchive(@NotNull String name) {
|
||||
boolean isName = true;
|
||||
final List<String> nameParts = new ArrayList<>();
|
||||
final List<String> versionParts = new ArrayList<>();
|
||||
|
||||
for (String part : StringUtil.split(name, "-")) {
|
||||
final boolean partStartsWithDigit = !part.isEmpty() && Character.isDigit(part.charAt(0));
|
||||
|
||||
if (partStartsWithDigit || "dev".equals(part)) {
|
||||
isName = false;
|
||||
}
|
||||
|
||||
if (isName) {
|
||||
nameParts.add(part);
|
||||
}
|
||||
else {
|
||||
versionParts.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return Pair.create(normalizeVcsOrArchiveNameParts(nameParts), normalizeVcsOrArchiveVersionParts(versionParts));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyRequirement createVcsOrArchiveRequirement(@NotNull Pair<String, String> nameAndVersion,
|
||||
@NotNull List<String> installOptions,
|
||||
@Nullable String extras) {
|
||||
final String name = nameAndVersion.getFirst();
|
||||
final String version = nameAndVersion.getSecond();
|
||||
|
||||
if (version == null) {
|
||||
if (extras == null) {
|
||||
return new PyRequirementImpl(name, Collections.emptyList(), installOptions, "");
|
||||
}
|
||||
else {
|
||||
return new PyRequirementImpl(name, Collections.emptyList(), installOptions, extras);
|
||||
}
|
||||
}
|
||||
|
||||
final List<PyRequirementVersionSpec> versionSpecs = Collections.singletonList(PyRequirementsKt.pyRequirementVersionSpec(
|
||||
PyRequirementRelation.EQ, version));
|
||||
if (extras == null) {
|
||||
return new PyRequirementImpl(name, versionSpecs, installOptions, "");
|
||||
}
|
||||
else {
|
||||
return new PyRequirementImpl(name, versionSpecs, installOptions, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyRequirement createVcsRequirement(@NotNull Matcher matcher) {
|
||||
final String path = matcher.group(PATH_IN_VCS_GROUP);
|
||||
final String egg = getEgg(matcher);
|
||||
|
||||
final String project = extractProject(dropTrunk(dropRevision(path)));
|
||||
final Pair<String, String> nameAndVersion =
|
||||
parseNameAndVersionFromVcsOrArchive(egg == null ? StringUtil.trimEnd(project, ".git") : egg);
|
||||
|
||||
return createVcsOrArchiveRequirement(nameAndVersion, calculateVcsInstallOptions(matcher), getVcsExtras(matcher));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirementVersionSpec> parseVersionSpecs(@Nullable String versionSpecs) {
|
||||
if (versionSpecs == null) return Collections.emptyList();
|
||||
|
||||
return StreamSupport
|
||||
.stream(StringUtil.tokenize(versionSpecs, ",").spliterator(), false)
|
||||
.map(String::trim)
|
||||
.map(PyRequirementParser::parseVersionSpec)
|
||||
.filter(req -> req != null)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> calculateRequirementInstallOptions(@NotNull Matcher matcher) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
result.add(matcher.group(REQUIREMENT_GROUP));
|
||||
|
||||
final String requirementOptions = matcher.group(REQUIREMENT_OPTIONS_GROUP);
|
||||
if (requirementOptions != null) {
|
||||
boolean isKey = true;
|
||||
for (String token : StringUtil.tokenize(requirementOptions, "\"")) {
|
||||
result.add(isKey ? token.substring(findFirstNotWhiteSpaceAfter(token, 0), token.length() - 1) : token);
|
||||
isKey = !isKey;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> splitByLinesAndCollapse(@NotNull String text) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String line : StringUtil.splitByLines(text)) {
|
||||
if (line.endsWith("\\") && !line.endsWith("\\\\")) {
|
||||
sb.append(line, 0, line.length() - 1);
|
||||
}
|
||||
else {
|
||||
if (sb.length() == 0) {
|
||||
result.add(line);
|
||||
}
|
||||
else {
|
||||
sb.append(line);
|
||||
|
||||
result.add(sb.toString());
|
||||
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> parseLine(@NotNull String line,
|
||||
@Nullable VirtualFile containingFile,
|
||||
@NotNull Set<VirtualFile> visitedFiles) {
|
||||
if (line.startsWith("-r")) {
|
||||
return parseRecursiveLine(line, containingFile, visitedFiles, "-r".length());
|
||||
}
|
||||
|
||||
if (line.startsWith("--requirement ")) {
|
||||
return parseRecursiveLine(line, containingFile, visitedFiles, "--requirement ".length());
|
||||
}
|
||||
|
||||
return Collections.singletonList(fromLine(line));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String normalizeVcsOrArchiveNameParts(@NotNull List<String> nameParts) {
|
||||
return normalizeName(StringUtil.join(nameParts, "-"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String normalizeVcsOrArchiveVersionParts(@NotNull List<String> versionParts) {
|
||||
return versionParts.isEmpty() ? null : normalizeVersion(StringUtil.join(versionParts, "-"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> calculateVcsInstallOptions(@NotNull Matcher matcher) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
|
||||
final String srcBefore = matcher.group(VCS_SRC_BEFORE_GROUP);
|
||||
if (srcBefore != null) {
|
||||
result.addAll(Arrays.asList(srcBefore.split("\\s+")));
|
||||
}
|
||||
|
||||
final String editable = matcher.group(VCS_EDITABLE_GROUP);
|
||||
if (editable != null) {
|
||||
result.add(editable);
|
||||
}
|
||||
|
||||
result.add(matcher.group(VCS_GROUP));
|
||||
|
||||
final String srcAfter = matcher.group(VCS_SRC_AFTER_GROUP);
|
||||
if (srcAfter != null) {
|
||||
result.addAll(Arrays.asList(srcAfter.split("\\s+")).subList(1, 3)); // skip spaces before --src and get only two values
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getEgg(@NotNull Matcher matcher) {
|
||||
final String beforeSubdir = matcher.group(VCS_EGG_BEFORE_SUBDIR_GROUP);
|
||||
|
||||
return beforeSubdir == null ? matcher.group(VCS_EGG_AFTER_SUBDIR_GROUP) : beforeSubdir;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String extractProject(@NotNull String path) {
|
||||
final int end = path.endsWith("/") ? path.length() - 1 : path.length();
|
||||
final int slashIndex = path.lastIndexOf("/", end - 1);
|
||||
|
||||
if (slashIndex != -1) {
|
||||
return path.substring(slashIndex + 1, end);
|
||||
}
|
||||
|
||||
if (end != path.length()) {
|
||||
return path.substring(0, end);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String dropTrunk(@NotNull String path) {
|
||||
final String slashTrunk = "/trunk";
|
||||
|
||||
if (path.endsWith(slashTrunk)) {
|
||||
return path.substring(0, path.length() - slashTrunk.length());
|
||||
}
|
||||
|
||||
final String slashTrunkSlash = "/trunk/";
|
||||
|
||||
if (path.endsWith(slashTrunkSlash)) {
|
||||
return path.substring(0, path.length() - slashTrunkSlash.length());
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String dropRevision(@NotNull String path) {
|
||||
final int atIndex = path.lastIndexOf("@");
|
||||
|
||||
if (atIndex != -1) {
|
||||
return path.substring(0, atIndex);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getVcsExtras(@NotNull Matcher matcher) {
|
||||
final String beforeSubdir = matcher.group(VCS_EXTRAS_BEFORE_SUBDIR_GROUP);
|
||||
|
||||
return beforeSubdir == null ? matcher.group(VCS_EXTRAS_AFTER_SUBDIR_GROUP) : beforeSubdir;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyRequirementVersionSpec parseVersionSpec(@NotNull String versionSpec) {
|
||||
PyRequirementRelation relation = null;
|
||||
|
||||
if (versionSpec.startsWith("===")) {
|
||||
relation = PyRequirementRelation.STR_EQ;
|
||||
}
|
||||
else if (versionSpec.startsWith("==")) {
|
||||
relation = PyRequirementRelation.EQ;
|
||||
}
|
||||
else if (versionSpec.startsWith("<=")) {
|
||||
relation = PyRequirementRelation.LTE;
|
||||
}
|
||||
else if (versionSpec.startsWith(">=")) {
|
||||
relation = PyRequirementRelation.GTE;
|
||||
}
|
||||
else if (versionSpec.startsWith("<")) {
|
||||
relation = PyRequirementRelation.LT;
|
||||
}
|
||||
else if (versionSpec.startsWith(">")) {
|
||||
relation = PyRequirementRelation.GT;
|
||||
}
|
||||
else if (versionSpec.startsWith("~=")) {
|
||||
relation = PyRequirementRelation.COMPATIBLE;
|
||||
}
|
||||
else if (versionSpec.startsWith("!=")) {
|
||||
relation = PyRequirementRelation.NE;
|
||||
}
|
||||
|
||||
if (relation != null) {
|
||||
final int versionIndex = findFirstNotWhiteSpaceAfter(versionSpec, relation.getPresentableText().length());
|
||||
return PyRequirementsKt.pyRequirementVersionSpec(relation, versionSpec.substring(versionIndex));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PyRequirement> parseRecursiveLine(@NotNull String line,
|
||||
@Nullable VirtualFile containingFile,
|
||||
@NotNull Set<VirtualFile> visitedFiles,
|
||||
int flagLength) {
|
||||
if (containingFile == null) return Collections.emptyList();
|
||||
|
||||
final int pathIndex = findFirstNotWhiteSpaceAfter(line, flagLength);
|
||||
if (pathIndex == line.length()) return Collections.emptyList();
|
||||
|
||||
final String path = FileUtil.toSystemIndependentName(line.substring(pathIndex));
|
||||
final VirtualFile file = findRecursiveFile(containingFile, path);
|
||||
|
||||
if (file != null && !visitedFiles.contains(file)) {
|
||||
return fromText(loadText(file), file, visitedFiles);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String normalizeName(@NotNull String s) {
|
||||
return s.replace('_', '-');
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String normalizeVersion(@NotNull String s) {
|
||||
return s.replace('_', '-').replaceAll("-?py[\\d\\.]+", "");
|
||||
}
|
||||
|
||||
private static int findFirstNotWhiteSpaceAfter(@NotNull String line, int beginIndex) {
|
||||
for (int i = beginIndex; i < line.length(); i++) {
|
||||
if (!StringUtil.isWhiteSpace(line.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return line.length();
|
||||
}
|
||||
|
||||
private static int findFirstNotWhiteSpaceBefore(@NotNull String line, int beginIndex) {
|
||||
for (int i = beginIndex; i >= 0; i--) {
|
||||
if (!StringUtil.isWhiteSpace(line.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static VirtualFile findRecursiveFile(@NotNull VirtualFile containingFile, @NotNull String path) {
|
||||
final VirtualFile dir = containingFile.getParent();
|
||||
if (dir == null) return null;
|
||||
|
||||
final VirtualFile file = dir.findFileByRelativePath(path);
|
||||
if (file != null) return file;
|
||||
|
||||
return LocalFileSystem.getInstance().findFileByPath(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec
|
||||
|
||||
/**
|
||||
* This helper is not an API, consider using methods listed below.
|
||||
*
|
||||
* @see PyPackageManager.parseRequirement
|
||||
* @see PyPackageManager.parseRequirements
|
||||
*
|
||||
* @see PyRequirementParser.fromLine
|
||||
* @see PyRequirementParser.fromText
|
||||
* @see PyRequirementParser.fromFile
|
||||
*/
|
||||
fun pyRequirement(name: String): PyRequirement = PyRequirementImpl(name, emptyList(), listOf(name), "")
|
||||
|
||||
/**
|
||||
* This helper is not an API, consider using methods listed below.
|
||||
* If given version could not be normalized, then specified relation will be replaced with [PyRequirementRelation.STR_EQ].
|
||||
*
|
||||
* @see PyPackageManager.parseRequirement
|
||||
* @see PyPackageManager.parseRequirements
|
||||
*
|
||||
* @see PyRequirementParser.fromLine
|
||||
* @see PyRequirementParser.fromText
|
||||
* @see PyRequirementParser.fromFile
|
||||
*
|
||||
* @see pyRequirementVersionSpec
|
||||
*/
|
||||
fun pyRequirement(name: String, relation: PyRequirementRelation, version: String): PyRequirement {
|
||||
val versionSpec = pyRequirementVersionSpec(relation, version)
|
||||
return PyRequirementImpl(name, listOf(versionSpec), listOf(name + relation.presentableText + version), "")
|
||||
}
|
||||
|
||||
/**
|
||||
* This method could be used to obtain [PyRequirementVersionSpec] instances with specified relation and version.
|
||||
* If given version could not be normalized, then specified relation will be replaced with [PyRequirementRelation.STR_EQ].
|
||||
*
|
||||
* @see PyPackageVersionNormalizer.normalize
|
||||
*/
|
||||
fun pyRequirementVersionSpec(relation: PyRequirementRelation, version: String): PyRequirementVersionSpec {
|
||||
if (relation == PyRequirementRelation.STR_EQ) return PyRequirementVersionSpecImpl(relation, null, version)
|
||||
|
||||
return PyPackageVersionNormalizer
|
||||
.normalize(version)
|
||||
.let {
|
||||
if (it == null) PyRequirementVersionSpecImpl(PyRequirementRelation.STR_EQ, null, version) else pyRequirementVersionSpec(relation, it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method could be used to obtain [PyRequirementVersionSpec] instances with specified relation and version.
|
||||
*
|
||||
* @see PyPackageVersion
|
||||
* @see PyPackageVersionNormalizer.normalize
|
||||
*/
|
||||
fun pyRequirementVersionSpec(relation: PyRequirementRelation, version: PyPackageVersion): PyRequirementVersionSpec {
|
||||
return PyRequirementVersionSpecImpl(relation, version, version.presentableText)
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of this class MUST be obtained from [pyRequirementVersionSpec].
|
||||
*/
|
||||
private data class PyRequirementVersionSpecImpl(private val relation: PyRequirementRelation,
|
||||
private val parsedVersion: PyPackageVersion?,
|
||||
private val version: String) : PyRequirementVersionSpec {
|
||||
|
||||
override fun getRelation() = relation
|
||||
override fun getVersion() = version
|
||||
|
||||
override fun matches(version: String): Boolean {
|
||||
val comparator = PyPackageVersionComparator.STR_COMPARATOR
|
||||
|
||||
return when (relation) {
|
||||
PyRequirementRelation.LT -> comparator.compare(version, this.version) < 0
|
||||
PyRequirementRelation.LTE -> comparator.compare(version, this.version) <= 0
|
||||
PyRequirementRelation.GT -> comparator.compare(version, this.version) > 0
|
||||
PyRequirementRelation.GTE -> comparator.compare(version, this.version) >= 0
|
||||
PyRequirementRelation.EQ -> {
|
||||
parsedVersion!!
|
||||
|
||||
val publicAndLocalVersions = splitIntoPublicAndLocalVersions(parsedVersion)
|
||||
val otherPublicAndLocalVersions = splitIntoPublicAndLocalVersions(version)
|
||||
val publicVersionsAreSame = comparator.compare(otherPublicAndLocalVersions.first, publicAndLocalVersions.first) == 0
|
||||
|
||||
return publicVersionsAreSame &&
|
||||
(publicAndLocalVersions.second.isEmpty() || otherPublicAndLocalVersions.second == publicAndLocalVersions.second)
|
||||
}
|
||||
PyRequirementRelation.NE -> comparator.compare(version, this.version) != 0
|
||||
PyRequirementRelation.COMPATIBLE -> {
|
||||
parsedVersion!!
|
||||
|
||||
return pyRequirementVersionSpec(PyRequirementRelation.GTE,
|
||||
parsedVersion).matches(version) &&
|
||||
pyRequirementVersionSpec(PyRequirementRelation.EQ,
|
||||
toEqPartOfCompatibleRelation(parsedVersion)).matches(version)
|
||||
}
|
||||
PyRequirementRelation.STR_EQ -> version == this.version
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitIntoPublicAndLocalVersions(version: PyPackageVersion): Pair<String, String> {
|
||||
return version.copy(local = null).presentableText to StringUtil.notNullize(version.local)
|
||||
}
|
||||
|
||||
private fun splitIntoPublicAndLocalVersions(version: String): Pair<String, String> {
|
||||
val publicAndLocalVersions = version.split('+', limit = 2)
|
||||
|
||||
val publicVersion = publicAndLocalVersions[0]
|
||||
val localVersion = if (publicAndLocalVersions.size == 1) "" else publicAndLocalVersions[1]
|
||||
|
||||
return publicVersion to localVersion
|
||||
}
|
||||
|
||||
private fun toEqPartOfCompatibleRelation(version: PyPackageVersion): PyPackageVersion {
|
||||
val release = version.release
|
||||
val lastPoint = release.lastIndexOf('.')
|
||||
|
||||
return if (lastPoint == -1) version
|
||||
else PyPackageVersion(version.epoch, release.substring(0, lastPoint + 1) + "*", null, null, null, null)
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class PyChooseRequirementsDialog extends DialogWrapper {
|
||||
myRequirementsChooser = new ElementsChooser<PyRequirement>(true) {
|
||||
@Override
|
||||
public String getItemText(@NotNull PyRequirement requirement) {
|
||||
return requirement.toString();
|
||||
return requirement.getPresentableText();
|
||||
}
|
||||
};
|
||||
myRequirementsChooser.setElements(requirements, true);
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.intellij.webcore.packaging.PackageManagementServiceEx;
|
||||
import com.intellij.webcore.packaging.RepoPackage;
|
||||
import com.jetbrains.python.packaging.*;
|
||||
import com.jetbrains.python.packaging.PyPIPackageUtil.PackageDetails;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.sdk.PySdkUtil;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
@@ -187,13 +188,9 @@ public class PyPackageManagementService extends PackageManagementServiceEx {
|
||||
if (forceUpgrade) {
|
||||
extraArgs.add("-U");
|
||||
}
|
||||
final PyRequirement req;
|
||||
if (version != null) {
|
||||
req = new PyRequirement(packageName, version);
|
||||
}
|
||||
else {
|
||||
req = new PyRequirement(packageName);
|
||||
}
|
||||
final PyRequirement req = version == null
|
||||
? PyRequirementsKt.pyRequirement(packageName)
|
||||
: PyRequirementsKt.pyRequirement(packageName, PyRequirementRelation.EQ, version);
|
||||
|
||||
final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() {
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.jetbrains.env.PyTestTask;
|
||||
import com.jetbrains.env.Staging;
|
||||
import com.jetbrains.python.packaging.PyPackage;
|
||||
import com.jetbrains.python.packaging.PyPackageManager;
|
||||
import com.jetbrains.python.packaging.PyRequirement;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
||||
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
|
||||
@@ -30,6 +30,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.testFramework.UsefulTestCase.assertInstanceOf;
|
||||
import static com.jetbrains.python.packaging.PyRequirementsKt.pyRequirement;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
@@ -116,8 +117,8 @@ public class PyPackagingTest extends PyEnvTestCase {
|
||||
final PyPackageManager manager = PyPackageManager.getInstance(venvSdk);
|
||||
final List<PyPackage> packages1 = manager.refreshAndGetPackages(false);
|
||||
// TODO: Install Markdown from a local file
|
||||
manager.install(list(PyRequirement.fromLine("Markdown<2.2"),
|
||||
new PyRequirement("httplib2")), Collections.emptyList());
|
||||
manager.install(Arrays.asList(pyRequirement("Markdown", PyRequirementRelation.LT, "2.2"), pyRequirement("httplib2")),
|
||||
Collections.emptyList());
|
||||
final List<PyPackage> packages2 = manager.refreshAndGetPackages(false);
|
||||
final PyPackage markdown2 = findPackage("Markdown", packages2);
|
||||
assertNotNull(markdown2);
|
||||
@@ -125,7 +126,7 @@ public class PyPackagingTest extends PyEnvTestCase {
|
||||
final PyPackage pip1 = findPackage("pip", packages1);
|
||||
assertNotNull(pip1);
|
||||
assertEquals("pip", pip1.getName());
|
||||
manager.uninstall(list(pip1));
|
||||
manager.uninstall(Collections.singletonList(pip1));
|
||||
final List<PyPackage> packages3 = manager.refreshAndGetPackages(false);
|
||||
final PyPackage pip2 = findPackage("pip", packages3);
|
||||
assertNull(pip2);
|
||||
@@ -150,11 +151,6 @@ public class PyPackagingTest extends PyEnvTestCase {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <T> List<T> list(T... xs) {
|
||||
return Arrays.asList(xs);
|
||||
}
|
||||
|
||||
|
||||
private abstract static class PyPackagingTestTask extends PyExecutionFixtureTestTask {
|
||||
PyPackagingTestTask() {
|
||||
super(null);
|
||||
|
||||
@@ -40,7 +40,7 @@ public class PyPackageTest extends TestCase {
|
||||
|
||||
@NotNull
|
||||
private static PyRequirement createRequirement(@NotNull String options) {
|
||||
final PyRequirement requirement = PyPackageUtil.fix(PyRequirement.fromLine(options));
|
||||
final PyRequirement requirement = PyRequirementParser.fromLine(options);
|
||||
assertNotNull(requirement);
|
||||
return requirement;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PyPackageUtilTest extends PyTestCase {
|
||||
// PY-18966
|
||||
public void testSetupPyDependencyLinksReading() {
|
||||
final List<PyRequirement> actual = PyPackageUtil.findSetupPyRequires(myFixture.getModule());
|
||||
final List<PyRequirement> expected = PyRequirement.fromText(
|
||||
final List<PyRequirement> expected = PyRequirementParser.fromText(
|
||||
"sqlalchemy >=1.0.12, <1.1\ngit+https://github.com/mysql/mysql-connector-python.git@2.1.3#egg=mysql-connector-python-2.1.3");
|
||||
|
||||
assertEquals(expected, actual);
|
||||
@@ -183,7 +183,7 @@ public class PyPackageUtilTest extends PyTestCase {
|
||||
}
|
||||
|
||||
private static void checkRequirements(@Nullable List<PyRequirement> actual, int fromIndex) {
|
||||
final List<PyRequirement> expected = PyRequirement.fromText("Markdown\nNewDjango==1.3.1\nnumpy\nmynose");
|
||||
final List<PyRequirement> expected = PyRequirementParser.fromText("Markdown\nNewDjango==1.3.1\nnumpy\nmynose");
|
||||
assertEquals(expected.subList(fromIndex, expected.size()), actual);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ public class PyPackageUtilTest extends PyTestCase {
|
||||
checkSetupArgumentText(module, keyword, "['NewDjango==1.3.1', 'Markdown']");
|
||||
|
||||
final List<PyRequirement> actual = PyPackageUtil.findSetupPyRequires(module);
|
||||
final List<PyRequirement> expected = PyRequirement.fromText("NewDjango==1.3.1\nMarkdown\nnumpy\nmynose");
|
||||
final List<PyRequirement> expected = PyRequirementParser.fromText("NewDjango==1.3.1\nMarkdown\nnumpy\nmynose");
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,60 +2,58 @@
|
||||
package com.jetbrains.python.packaging
|
||||
|
||||
import com.jetbrains.python.fixtures.PyTestCase
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersion
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionNormalizer
|
||||
import one.util.streamex.StreamEx
|
||||
|
||||
class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
|
||||
fun testEpoch() {
|
||||
check(PyPackageVersion("0", "1.2.3"),
|
||||
PyPackageVersion("0", "1.2.3"),
|
||||
PyPackageVersion("1", "1.2.3"))
|
||||
check(normalize("0!1.2.3"),
|
||||
normalize("0!1.2.3"),
|
||||
normalize("1!1.2.3"))
|
||||
}
|
||||
|
||||
fun testSameLengthRelease() {
|
||||
check(PyPackageVersion(release = "1.2"),
|
||||
PyPackageVersion(release = "1.2"),
|
||||
PyPackageVersion(release = "1.3"))
|
||||
check(normalize("1.2"),
|
||||
normalize("1.2"),
|
||||
normalize("1.3"))
|
||||
|
||||
check(PyPackageVersion(release = "1.2.3"),
|
||||
PyPackageVersion(release = "1.2.3"),
|
||||
PyPackageVersion(release = "1.2.4"))
|
||||
check(normalize("1.2.3"),
|
||||
normalize("1.2.3"),
|
||||
normalize("1.2.4"))
|
||||
}
|
||||
|
||||
fun testDifferentLengthRelease() {
|
||||
check(PyPackageVersion(release = "1.2"),
|
||||
PyPackageVersion(release = "1.2.0"),
|
||||
PyPackageVersion(release = "1.2.1"))
|
||||
check(normalize("1.2"),
|
||||
normalize("1.2.0"),
|
||||
normalize("1.2.1"))
|
||||
|
||||
check(PyPackageVersion(release = "1.2.3"),
|
||||
PyPackageVersion(release = "1.2.3.0"),
|
||||
PyPackageVersion(release = "1.2.3.1"))
|
||||
check(normalize("1.2.3"),
|
||||
normalize("1.2.3.0"),
|
||||
normalize("1.2.3.1"))
|
||||
}
|
||||
|
||||
fun testPost() {
|
||||
check(PyPackageVersion(release = "1.2", post = "post1"),
|
||||
PyPackageVersion(release = "1.2", post = "post1"),
|
||||
PyPackageVersion(release = "1.2", post = "post2"))
|
||||
check(normalize("1.2.post1"),
|
||||
normalize("1.2.post1"),
|
||||
normalize("1.2.post2"))
|
||||
}
|
||||
|
||||
fun testPre() {
|
||||
check(PyPackageVersion(release = "1.2", pre = "a1"),
|
||||
PyPackageVersion(release = "1.2", pre = "a1"),
|
||||
PyPackageVersion(release = "1.2", pre = "a2"))
|
||||
check(normalize("1.2a1"),
|
||||
normalize("1.2a1"),
|
||||
normalize("1.2a2"))
|
||||
}
|
||||
|
||||
fun testDev() {
|
||||
check(PyPackageVersion(release = "1.2", dev = "dev1"),
|
||||
PyPackageVersion(release = "1.2", dev = "dev1"),
|
||||
PyPackageVersion(release = "1.2", dev = "dev2"))
|
||||
check(normalize("1.2.dev1"),
|
||||
normalize("1.2.dev1"),
|
||||
normalize("1.2.dev2"))
|
||||
}
|
||||
|
||||
fun testLocal() {
|
||||
check(PyPackageVersion(release = "1.2", local = "abc"),
|
||||
PyPackageVersion(release = "1.2", local = "abc"),
|
||||
PyPackageVersion(release = "1.2", local = "def"))
|
||||
check(normalize("1.2+abc"),
|
||||
normalize("1.2+abc"),
|
||||
normalize("1.2+def"))
|
||||
}
|
||||
|
||||
fun testSameReleaseOrder() {
|
||||
@@ -71,7 +69,7 @@ class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
normalize("1.0.c1.dev1"),
|
||||
normalize("1.0.c1"),
|
||||
normalize("1.0.c1.post1"),
|
||||
PyPackageVersion(release = "1.0"),
|
||||
normalize("1.0"),
|
||||
normalize("1.0.post1.dev1"),
|
||||
normalize("1.0.post1")
|
||||
)
|
||||
@@ -91,7 +89,7 @@ class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
normalize("1.0.c1.dev1"),
|
||||
normalize("1.0.c1"),
|
||||
normalize("1.0.c1.post1"),
|
||||
PyPackageVersion(release = "1.0"),
|
||||
normalize("1.0"),
|
||||
normalize("1.0.post1.dev1"),
|
||||
normalize("1.0.post1"),
|
||||
|
||||
@@ -105,7 +103,7 @@ class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
normalize("2.0.c1.dev1"),
|
||||
normalize("2.0.c1"),
|
||||
normalize("2.0.c1.post1"),
|
||||
PyPackageVersion(release = "2.0"),
|
||||
normalize("2.0"),
|
||||
normalize("2.0.post1.dev1"),
|
||||
normalize("2.0.post1")
|
||||
)
|
||||
@@ -113,7 +111,7 @@ class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
}
|
||||
|
||||
fun testCompatible() {
|
||||
val pkg = PyPackageVersion(release = "1.*")
|
||||
val pkg = normalize("1.*")
|
||||
|
||||
listOf(
|
||||
normalize("1.0.dev1"),
|
||||
@@ -126,16 +124,14 @@ class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
normalize("1.0.c1.dev1"),
|
||||
normalize("1.0.c1"),
|
||||
normalize("1.0.c1.post1"),
|
||||
PyPackageVersion(release = "1.0"),
|
||||
normalize("1.0"),
|
||||
normalize("1.0.post1.dev1"),
|
||||
normalize("1.0.post1")
|
||||
)
|
||||
.forEach { check(it, pkg, true) }
|
||||
}
|
||||
|
||||
private fun normalize(version: String) = PyRequirementVersionNormalizer.normalize(version)!!.toPkgVersion()
|
||||
|
||||
private fun PyRequirementVersion.toPkgVersion() = PyPackageVersion(epoch, release, pre, post, dev, local)
|
||||
private fun normalize(version: String) = PyPackageVersionNormalizer.normalize(version)!!
|
||||
|
||||
private fun check(less: PyPackageVersion, equal: PyPackageVersion, greater: PyPackageVersion) {
|
||||
check(less, equal, true)
|
||||
@@ -155,17 +151,17 @@ class PyPackageVersionComparatorTest : PyTestCase() {
|
||||
assertFalse(message, pkg1 === pkg2)
|
||||
|
||||
assertTrue(message, PyPackageVersionComparator.compare(pkg1, pkg2) == 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg1.toString(), pkg2.toString()) == 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg1.presentableText, pkg2.presentableText) == 0)
|
||||
|
||||
assertTrue(message, PyPackageVersionComparator.compare(pkg2, pkg1) == 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg2.toString(), pkg1.toString()) == 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg2.presentableText, pkg1.presentableText) == 0)
|
||||
}
|
||||
else {
|
||||
assertTrue(message, PyPackageVersionComparator.compare(pkg1, pkg2) < 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg1.toString(), pkg2.toString()) < 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg1.presentableText, pkg2.presentableText) < 0)
|
||||
|
||||
assertTrue(message, PyPackageVersionComparator.compare(pkg2, pkg1) > 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg2.toString(), pkg1.toString()) > 0)
|
||||
assertTrue(message, PyPackageVersionComparator.STR_COMPARATOR.compare(pkg2.presentableText, pkg1.presentableText) > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,20 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersion;
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.packaging.PyPackageUtil.fix;
|
||||
import static com.jetbrains.python.packaging.PyRequirementParser.fromLine;
|
||||
import static com.jetbrains.python.packaging.PyRequirementsKt.pyRequirement;
|
||||
import static com.jetbrains.python.packaging.PyRequirementsKt.pyRequirementVersionSpec;
|
||||
import static com.jetbrains.python.packaging.requirement.PyRequirementRelation.*;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@@ -1901,50 +1904,37 @@ public class PyRequirementTest extends PyTestCase {
|
||||
public void testMinusInRequirementEggName() {
|
||||
final String line = "git://github.com/toastdriven/django-haystack.git#egg=django-haystack";
|
||||
|
||||
assertEquals(new PyRequirement("django-haystack", Collections.emptyList(), Collections.singletonList(line)),
|
||||
PyRequirement.fromLine(line));
|
||||
assertEquals(new PyRequirementImpl("django-haystack", emptyList(), singletonList(line), ""), fromLine(line));
|
||||
}
|
||||
|
||||
public void testDevInRequirementEggName() {
|
||||
doTest("django-haystack", "dev", "git://github.com/toastdriven/django-haystack.git#egg=django_haystack-dev");
|
||||
doTest("django-haystack", "dev", "git://github.com/toastdriven/django-haystack.git#egg=django-haystack-dev");
|
||||
doTest("django-haystack", "dev", "git://github.com/toastdriven/django-haystack.git#egg=django_haystack-dev");
|
||||
}
|
||||
|
||||
// PY-26844
|
||||
public void testExtrasInRequirementEggName() {
|
||||
final String line1 = "git://github.com/python-social-auth/social-core.git#egg=social-auth-core[openidconnect]";
|
||||
assertEquals(new PyRequirement("social-auth-core", Collections.emptyList(), Collections.singletonList(line1), "[openidconnect]"),
|
||||
PyRequirement.fromLine(line1));
|
||||
assertEquals(new PyRequirementImpl("social-auth-core", emptyList(), singletonList(line1), "[openidconnect]"), fromLine(line1));
|
||||
|
||||
final String line2 = "git://github.com/python-social-auth/social-core.git#egg=social-auth-core[openidconnect,security]";
|
||||
assertEquals(
|
||||
new PyRequirement("social-auth-core", Collections.emptyList(), Collections.singletonList(line2), "[openidconnect,security]"),
|
||||
PyRequirement.fromLine(line2)
|
||||
);
|
||||
assertEquals(new PyRequirementImpl("social-auth-core", emptyList(), singletonList(line2), "[openidconnect,security]"), fromLine(line2));
|
||||
|
||||
final String line3 =
|
||||
"git://github.com/python-social-auth/social-core.git#egg=social-auth-core[openidconnect]&subdirectory=clients/python";
|
||||
assertEquals(new PyRequirement("social-auth-core", Collections.emptyList(), Collections.singletonList(line3), "[openidconnect]"),
|
||||
PyRequirement.fromLine(line3));
|
||||
assertEquals(new PyRequirementImpl("social-auth-core", emptyList(), singletonList(line3), "[openidconnect]"), fromLine(line3));
|
||||
|
||||
final String line4 =
|
||||
"git://github.com/python-social-auth/social-core.git#egg=social-auth-core[openidconnect,security]&subdirectory=clients/python";
|
||||
assertEquals(
|
||||
new PyRequirement("social-auth-core", Collections.emptyList(), Collections.singletonList(line4), "[openidconnect,security]"),
|
||||
PyRequirement.fromLine(line4)
|
||||
);
|
||||
assertEquals(new PyRequirementImpl("social-auth-core", emptyList(), singletonList(line4), "[openidconnect,security]"), fromLine(line4));
|
||||
|
||||
final String line5 =
|
||||
"git://github.com/python-social-auth/social-core.git#subdirectory=clients/python&egg=social-auth-core[openidconnect]";
|
||||
assertEquals(new PyRequirement("social-auth-core", Collections.emptyList(), Collections.singletonList(line5), "[openidconnect]"),
|
||||
PyRequirement.fromLine(line5));
|
||||
assertEquals(new PyRequirementImpl("social-auth-core", emptyList(), singletonList(line5), "[openidconnect]"), fromLine(line5));
|
||||
|
||||
final String line6 =
|
||||
"git://github.com/python-social-auth/social-core.git#subdirectory=clients/python&egg=social-auth-core[openidconnect,security]";
|
||||
assertEquals(
|
||||
new PyRequirement("social-auth-core", Collections.emptyList(), Collections.singletonList(line6), "[openidconnect,security]"),
|
||||
PyRequirement.fromLine(line6)
|
||||
);
|
||||
assertEquals(new PyRequirementImpl("social-auth-core", emptyList(), singletonList(line6), "[openidconnect,security]"), fromLine(line6));
|
||||
}
|
||||
|
||||
// LOCAL DIR
|
||||
@@ -1958,29 +1948,28 @@ public class PyRequirementTest extends PyTestCase {
|
||||
// TODO: hashes
|
||||
// https://www.python.org/dev/peps/pep-0508/#names
|
||||
public void testRequirement() {
|
||||
assertEquals(new PyRequirement("Orange-Bioinformatics"), PyRequirement.fromLine("Orange-Bioinformatics"));
|
||||
assertEquals(new PyRequirement("MOCPy"), PyRequirement.fromLine("MOCPy"));
|
||||
assertEquals(new PyRequirement("score.webassets"), PyRequirement.fromLine("score.webassets"));
|
||||
assertEquals(new PyRequirement("pip_helpers"), PyRequirement.fromLine("pip_helpers"));
|
||||
assertEquals(new PyRequirement("Django"), PyRequirement.fromLine("Django"));
|
||||
assertEquals(new PyRequirement("django"), PyRequirement.fromLine("django"));
|
||||
assertEquals(new PyRequirement("pinax-utils"), PyRequirement.fromLine("pinax-utils"));
|
||||
assertEquals(new PyRequirement("no_limit_nester"), PyRequirement.fromLine("no_limit_nester"));
|
||||
assertEquals(new PyRequirement("Flask-Celery-py3"), PyRequirement.fromLine("Flask-Celery-py3"));
|
||||
assertEquals(pyRequirement("Orange-Bioinformatics"), fromLine("Orange-Bioinformatics"));
|
||||
assertEquals(pyRequirement("MOCPy"), fromLine("MOCPy"));
|
||||
assertEquals(pyRequirement("score.webassets"), fromLine("score.webassets"));
|
||||
assertEquals(pyRequirement("pip_helpers"), fromLine("pip_helpers"));
|
||||
assertEquals(pyRequirement("Django"), fromLine("Django"));
|
||||
assertEquals(pyRequirement("django"), fromLine("django"));
|
||||
assertEquals(pyRequirement("pinax-utils"), fromLine("pinax-utils"));
|
||||
assertEquals(pyRequirement("no_limit_nester"), fromLine("no_limit_nester"));
|
||||
assertEquals(pyRequirement("Flask-Celery-py3"), fromLine("Flask-Celery-py3"));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/
|
||||
public void testRequirementVersion() {
|
||||
assertEquals(new PyRequirement("Orange-Bioinformatics", "2.5a20"), PyRequirement.fromLine("Orange-Bioinformatics==2.5a20"));
|
||||
assertEquals(new PyRequirement("MOCPy", "0.1.0.dev0"), PyRequirement.fromLine("MOCPy==0.1.0.dev0"));
|
||||
assertEquals(new PyRequirement("score.webassets", "0.2.3"), PyRequirement.fromLine("score.webassets==0.2.3"));
|
||||
assertEquals(new PyRequirement("pip_helpers", "0.5.post6"), PyRequirement.fromLine("pip_helpers==0.5.post6"));
|
||||
assertEquals(new PyRequirement("Django", "1.9rc1"), PyRequirement.fromLine("Django==1.9rc1"));
|
||||
assertEquals(new PyRequirement("django", "1!1"), PyRequirement.fromLine("django==1!1"));
|
||||
assertEquals(new PyRequirement("pinax-utils", "1.0b1.dev3"), PyRequirement.fromLine("pinax-utils==1.0b1.dev3"));
|
||||
assertEquals(new PyRequirement("Flask-Celery-py3", "0.1.*"), PyRequirement.fromLine("Flask-Celery-py3==0.1.*"));
|
||||
assertEquals(new PyRequirement("no_limit_nester", "1.0+local.version.10"),
|
||||
PyRequirement.fromLine("no_limit_nester==1.0+local.version.10"));
|
||||
assertEquals(pyRequirement("Orange-Bioinformatics", EQ, "2.5a20"), fromLine("Orange-Bioinformatics==2.5a20"));
|
||||
assertEquals(pyRequirement("MOCPy", EQ, "0.1.0.dev0"), fromLine("MOCPy==0.1.0.dev0"));
|
||||
assertEquals(pyRequirement("score.webassets", EQ, "0.2.3"), fromLine("score.webassets==0.2.3"));
|
||||
assertEquals(pyRequirement("pip_helpers", EQ, "0.5.post6"), fromLine("pip_helpers==0.5.post6"));
|
||||
assertEquals(pyRequirement("Django", EQ, "1.9rc1"), fromLine("Django==1.9rc1"));
|
||||
assertEquals(pyRequirement("django", EQ, "1!1"), fromLine("django==1!1"));
|
||||
assertEquals(pyRequirement("pinax-utils", EQ, "1.0b1.dev3"), fromLine("pinax-utils==1.0b1.dev3"));
|
||||
assertEquals(pyRequirement("Flask-Celery-py3", EQ, "0.1.*"), fromLine("Flask-Celery-py3==0.1.*"));
|
||||
assertEquals(pyRequirement("no_limit_nester", EQ, "1.0+local.version.10"), fromLine("no_limit_nester==1.0+local.version.10"));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#normalization
|
||||
@@ -2116,7 +2105,7 @@ public class PyRequirementTest extends PyTestCase {
|
||||
|
||||
// PY-20223
|
||||
public void testRequirementVersionWithBigInteger() {
|
||||
assertEquals(new PyRequirement("pkg-name", "3.4.201607251407"), PyRequirement.fromLine("pkg-name==3.4.201607251407"));
|
||||
assertEquals(pyRequirement("pkg-name", EQ, "3.4.201607251407"), fromLine("pkg-name==3.4.201607251407"));
|
||||
}
|
||||
|
||||
// PY-11835
|
||||
@@ -2124,55 +2113,44 @@ public class PyRequirementTest extends PyTestCase {
|
||||
final String name = "django_compressor";
|
||||
final String version = "dev";
|
||||
final String line = name + "==" + version;
|
||||
final List<PyRequirementVersionSpec> versionSpecs = Collections.singletonList(new PyRequirementVersionSpec(version));
|
||||
final List<PyRequirementVersionSpec> versionSpecs = singletonList(pyRequirementVersionSpec(STR_EQ, version));
|
||||
|
||||
assertEquals(new PyRequirement(name, versionSpecs, Collections.singletonList(line)), PyRequirement.fromLine(line));
|
||||
assertEquals(new PyRequirementImpl(name, versionSpecs, singletonList(line), ""), fromLine(line));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#version-specifiers
|
||||
public void testRequirementRelation() {
|
||||
doRequirementRelationTest(PyRequirementRelation.LT, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(PyRequirementRelation.LTE, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(PyRequirementRelation.NE, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(PyRequirementRelation.EQ, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(PyRequirementRelation.GT, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(PyRequirementRelation.GTE, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(PyRequirementRelation.COMPATIBLE, PyRequirementVersion.release("1.*"));
|
||||
doRequirementRelationTest(PyRequirementRelation.STR_EQ, PyRequirementVersion.release("version"));
|
||||
doRequirementRelationTest(LT, release("1.4"));
|
||||
doRequirementRelationTest(LTE, release("1.4"));
|
||||
doRequirementRelationTest(NE, release("1.4"));
|
||||
doRequirementRelationTest(EQ, release("1.4"));
|
||||
doRequirementRelationTest(GT, release("1.4"));
|
||||
doRequirementRelationTest(GTE, release("1.4"));
|
||||
doRequirementRelationTest(COMPATIBLE, release("1.*"));
|
||||
|
||||
doRequirementRelationTest(Arrays.asList(PyRequirementRelation.GTE, PyRequirementRelation.EQ),
|
||||
Arrays.asList(PyRequirementVersion.release("2.8.1"), PyRequirementVersion.release("2.8.*")));
|
||||
doRequirementRelationTest(Arrays.asList(PyRequirementRelation.LT, PyRequirementRelation.GTE),
|
||||
Arrays.asList(PyRequirementVersion.release("1.4"), PyRequirementVersion.release("1.3.1")));
|
||||
assertEquals(pyRequirement("name", STR_EQ, "version"), fromLine("name===version"));
|
||||
|
||||
doRequirementRelationTest(Arrays.asList(PyRequirementRelation.LT,
|
||||
PyRequirementRelation.GT,
|
||||
PyRequirementRelation.NE,
|
||||
PyRequirementRelation.LT,
|
||||
PyRequirementRelation.EQ),
|
||||
Arrays.asList(PyRequirementVersion.release("1.6"),
|
||||
PyRequirementVersion.release("1.9"),
|
||||
PyRequirementVersion.release("1.9.6"),
|
||||
new PyRequirementVersion(null, "2.0", "a0", null, null, null),
|
||||
new PyRequirementVersion(null, "2.4", "rc1", null, null, null)));
|
||||
doRequirementRelationTest(Arrays.asList(GTE, EQ), Arrays.asList(release("2.8.1"), release("2.8.*")));
|
||||
doRequirementRelationTest(Arrays.asList(LT, GTE), Arrays.asList(release("1.4"), release("1.3.1")));
|
||||
|
||||
doRequirementRelationTest(Arrays.asList(LT, GT, NE, LT, EQ),
|
||||
Arrays.asList(release("1.6"),
|
||||
release("1.9"),
|
||||
release("1.9.6"),
|
||||
new PyPackageVersion(null, "2.0", "a0", null, null, null),
|
||||
new PyPackageVersion(null, "2.4", "rc1", null, null, null)));
|
||||
|
||||
// PY-14583
|
||||
doRequirementRelationTest(Arrays.asList(PyRequirementRelation.GTE,
|
||||
PyRequirementRelation.LTE,
|
||||
PyRequirementRelation.GTE,
|
||||
PyRequirementRelation.LTE),
|
||||
Arrays.asList(PyRequirementVersion.release("0.8.4"),
|
||||
PyRequirementVersion.release("0.8.99"),
|
||||
PyRequirementVersion.release("0.9.7"),
|
||||
PyRequirementVersion.release("0.9.99")));
|
||||
doRequirementRelationTest(Arrays.asList(GTE, LTE, GTE, LTE),
|
||||
Arrays.asList(release("0.8.4"), release("0.8.99"), release("0.9.7"), release("0.9.99")));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0508/#extras
|
||||
// PY-15674
|
||||
public void testRequirementExtras() {
|
||||
final String name = "MyProject1";
|
||||
final List<PyRequirementRelation> relations = Collections.emptyList();
|
||||
final List<PyRequirementVersion> versions = Collections.emptyList();
|
||||
final List<PyRequirementRelation> relations = emptyList();
|
||||
final List<PyPackageVersion> versions = emptyList();
|
||||
|
||||
doRequirementRelationTest(name, "[PDF]", relations, versions);
|
||||
doRequirementRelationTest(name, " [extra1, extra2]", relations, versions);
|
||||
@@ -2191,49 +2169,42 @@ public class PyRequirementTest extends PyTestCase {
|
||||
final String extras3 = " [security,tests]";
|
||||
final String name3 = "requests";
|
||||
|
||||
doRequirementRelationTest(name1, extras1, PyRequirementRelation.LT, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(name2, extras2, PyRequirementRelation.LTE, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(name3, extras3, PyRequirementRelation.NE, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(name1, extras1, PyRequirementRelation.EQ, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(name2, extras2, PyRequirementRelation.GT, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(name3, extras3, PyRequirementRelation.GTE, PyRequirementVersion.release("1.4"));
|
||||
doRequirementRelationTest(name1, extras1, PyRequirementRelation.COMPATIBLE, PyRequirementVersion.release("1.*"));
|
||||
doRequirementRelationTest(name2, extras2, PyRequirementRelation.STR_EQ, PyRequirementVersion.release("version"));
|
||||
doRequirementRelationTest(name1, extras1, LT, release("1.4"));
|
||||
doRequirementRelationTest(name2, extras2, LTE, release("1.4"));
|
||||
doRequirementRelationTest(name3, extras3, NE, release("1.4"));
|
||||
doRequirementRelationTest(name1, extras1, EQ, release("1.4"));
|
||||
doRequirementRelationTest(name2, extras2, GT, release("1.4"));
|
||||
doRequirementRelationTest(name3, extras3, GTE, release("1.4"));
|
||||
doRequirementRelationTest(name1, extras1, COMPATIBLE, release("1.*"));
|
||||
|
||||
doRequirementRelationTest(name3,
|
||||
extras3,
|
||||
Arrays.asList(PyRequirementRelation.GTE, PyRequirementRelation.EQ),
|
||||
Arrays.asList(PyRequirementVersion.release("2.8.1"), PyRequirementVersion.release("2.8.*")));
|
||||
final String line1 = name2 + extras2 + STR_EQ.getPresentableText() + "version";
|
||||
assertEquals(
|
||||
new PyRequirementImpl(
|
||||
name2,
|
||||
singletonList(pyRequirementVersionSpec(STR_EQ, "version")),
|
||||
singletonList(line1),
|
||||
StringUtil.trimLeading(extras2)
|
||||
),
|
||||
fromLine(line1)
|
||||
);
|
||||
|
||||
doRequirementRelationTest(name1,
|
||||
extras1,
|
||||
Arrays.asList(PyRequirementRelation.LT, PyRequirementRelation.GTE),
|
||||
Arrays.asList(PyRequirementVersion.release("1.4"), PyRequirementVersion.release("1.3.1")));
|
||||
doRequirementRelationTest(name3, extras3, Arrays.asList(GTE, EQ), Arrays.asList(release("2.8.1"), release("2.8.*")));
|
||||
doRequirementRelationTest(name1, extras1, Arrays.asList(LT, GTE), Arrays.asList(release("1.4"), release("1.3.1")));
|
||||
|
||||
doRequirementRelationTest(name2,
|
||||
extras2,
|
||||
Arrays.asList(PyRequirementRelation.LT,
|
||||
PyRequirementRelation.GT,
|
||||
PyRequirementRelation.NE,
|
||||
PyRequirementRelation.LT,
|
||||
PyRequirementRelation.EQ),
|
||||
Arrays.asList(PyRequirementVersion.release("1.6"),
|
||||
PyRequirementVersion.release("1.9"),
|
||||
PyRequirementVersion.release("1.9.6"),
|
||||
new PyRequirementVersion(null, "2.0", "a0", null, null, null),
|
||||
new PyRequirementVersion(null, "2.4", "rc1", null, null, null)));
|
||||
Arrays.asList(LT, GT, NE, LT, EQ),
|
||||
Arrays.asList(release("1.6"),
|
||||
release("1.9"),
|
||||
release("1.9.6"),
|
||||
new PyPackageVersion(null, "2.0", "a0", null, null, null),
|
||||
new PyPackageVersion(null, "2.4", "rc1", null, null, null)));
|
||||
|
||||
// PY-14583
|
||||
doRequirementRelationTest(name3,
|
||||
extras3,
|
||||
Arrays.asList(PyRequirementRelation.GTE,
|
||||
PyRequirementRelation.LTE,
|
||||
PyRequirementRelation.GTE,
|
||||
PyRequirementRelation.LTE),
|
||||
Arrays.asList(PyRequirementVersion.release("0.8.4"),
|
||||
PyRequirementVersion.release("0.8.99"),
|
||||
PyRequirementVersion.release("0.9.7"),
|
||||
PyRequirementVersion.release("0.9.99")));
|
||||
Arrays.asList(GTE, LTE, GTE, LTE),
|
||||
Arrays.asList(release("0.8.4"), release("0.8.99"), release("0.9.7"), release("0.9.99")));
|
||||
}
|
||||
|
||||
// https://pip.pypa.io/en/stable/reference/pip_install/#per-requirement-overrides
|
||||
@@ -2242,8 +2213,7 @@ public class PyRequirementTest extends PyTestCase {
|
||||
final String version = "1.2";
|
||||
final String linePrefix = name + " >= " + version;
|
||||
|
||||
final List<PyRequirementVersionSpec> versionSpecs =
|
||||
Collections.singletonList(new PyRequirementVersionSpec(PyRequirementRelation.GTE, PyRequirementVersion.release(version)));
|
||||
final List<PyRequirementVersionSpec> versionSpecs = singletonList(pyRequirementVersionSpec(GTE, release(version)));
|
||||
|
||||
final List<String> installOptions1 = Arrays.asList(linePrefix,
|
||||
"--global-option", "--no-user-cfg",
|
||||
@@ -2253,11 +2223,11 @@ public class PyRequirementTest extends PyTestCase {
|
||||
"--global-option=\"--no-user-cfg\" " +
|
||||
"--install-option=\"--prefix='/usr/local'\" " +
|
||||
"--install-option=\"--no-compile\"";
|
||||
assertEquals(new PyRequirement(name, versionSpecs, installOptions1), PyRequirement.fromLine(line1));
|
||||
assertEquals(new PyRequirementImpl(name, versionSpecs, installOptions1, ""), fromLine(line1));
|
||||
|
||||
final List<String> installOptions2 = Arrays.asList(linePrefix, "--install-option", "--install-scripts=/usr/local/bin");
|
||||
final String line2 = linePrefix + " --install-option=\"--install-scripts=/usr/local/bin\"";
|
||||
assertEquals(new PyRequirement(name, versionSpecs, installOptions2), PyRequirement.fromLine(line2));
|
||||
assertEquals(new PyRequirementImpl(name, versionSpecs, installOptions2, ""), fromLine(line2));
|
||||
}
|
||||
|
||||
public void testMultilineRequirement() {
|
||||
@@ -2265,8 +2235,7 @@ public class PyRequirementTest extends PyTestCase {
|
||||
final String version = "1.2";
|
||||
final String textPrefix = name + " >= " + version;
|
||||
|
||||
final List<PyRequirementVersionSpec> versionSpecs =
|
||||
Collections.singletonList(new PyRequirementVersionSpec(PyRequirementRelation.GTE, PyRequirementVersion.release(version)));
|
||||
final List<PyRequirementVersionSpec> versionSpecs = singletonList(pyRequirementVersionSpec(GTE, release(version)));
|
||||
|
||||
final String text = textPrefix + " " +
|
||||
"--global-option=\"--no-user-cfg\" \\\n" +
|
||||
@@ -2278,133 +2247,133 @@ public class PyRequirementTest extends PyTestCase {
|
||||
"--install-option", "--prefix='/usr/local'",
|
||||
"--install-option", "--no-compile");
|
||||
|
||||
assertEquals(Collections.singletonList(new PyRequirement(name, versionSpecs, installOptions)), PyRequirement.fromText(text));
|
||||
assertEquals(singletonList(new PyRequirementImpl(name, versionSpecs, installOptions, "")), PyRequirementParser.fromText(text));
|
||||
}
|
||||
|
||||
// PY-6355
|
||||
public void testTrailingZeroesInVersion() {
|
||||
final PyRequirement req = fix(PyRequirement.fromLine("foo==0.8.0"));
|
||||
final PyPackage pkg = new PyPackage("foo", "0.8", null, Collections.emptyList());
|
||||
final PyRequirement req = fromLine("foo==0.8.0");
|
||||
final PyPackage pkg = new PyPackage("foo", "0.8", null, emptyList());
|
||||
assertNotNull(req);
|
||||
assertEquals(pkg, req.match(Collections.singletonList(pkg)));
|
||||
assertEquals(pkg, req.match(singletonList(pkg)));
|
||||
}
|
||||
|
||||
// PY-6438
|
||||
public void testUnderscoreMatchesDash() {
|
||||
final PyRequirement req = fix(PyRequirement.fromLine("pyramid_zcml"));
|
||||
final PyPackage pkg = new PyPackage("pyramid-zcml", "0.1", null, Collections.emptyList());
|
||||
final PyRequirement req = fromLine("pyramid_zcml");
|
||||
final PyPackage pkg = new PyPackage("pyramid-zcml", "0.1", null, emptyList());
|
||||
assertNotNull(req);
|
||||
assertEquals(pkg, req.match(Collections.singletonList(pkg)));
|
||||
assertEquals(pkg, req.match(singletonList(pkg)));
|
||||
}
|
||||
|
||||
// PY-20242
|
||||
public void testVersionInterpretedAsString() {
|
||||
final PyRequirement req = fix(PyRequirement.fromLine("foo===version"));
|
||||
final PyPackage pkg = new PyPackage("foo", "version", null, Collections.emptyList());
|
||||
final PyRequirement req = fromLine("foo===version");
|
||||
final PyPackage pkg = new PyPackage("foo", "version", null, emptyList());
|
||||
assertNotNull(req);
|
||||
assertEquals(pkg, req.match(Collections.singletonList(pkg)));
|
||||
assertEquals(pkg, req.match(singletonList(pkg)));
|
||||
}
|
||||
|
||||
// PY-20880
|
||||
public void testMatchingLocalVersions() {
|
||||
final PyPackage firstPackageWithLocalVersion = new PyPackage("foo", "1.0+foo0100", null, Collections.emptyList());
|
||||
final PyPackage secondPackageWithLocalVersion = new PyPackage("foo", "1.0+foo0101", null, Collections.emptyList());
|
||||
final PyPackage firstPackageWithLocalVersion = new PyPackage("foo", "1.0+foo0100", null, emptyList());
|
||||
final PyPackage secondPackageWithLocalVersion = new PyPackage("foo", "1.0+foo0101", null, emptyList());
|
||||
|
||||
final PyRequirement requirement = fix(PyRequirement.fromLine("foo==1.0"));
|
||||
assertEquals(firstPackageWithLocalVersion, requirement.match(Collections.singletonList(firstPackageWithLocalVersion)));
|
||||
assertEquals(secondPackageWithLocalVersion, requirement.match(Collections.singletonList(secondPackageWithLocalVersion)));
|
||||
final PyRequirement requirement = fromLine("foo==1.0");
|
||||
assertEquals(firstPackageWithLocalVersion, requirement.match(singletonList(firstPackageWithLocalVersion)));
|
||||
assertEquals(secondPackageWithLocalVersion, requirement.match(singletonList(secondPackageWithLocalVersion)));
|
||||
|
||||
final PyRequirement requirementWithLocalVersion = fix(PyRequirement.fromLine("foo==1.0+foo0100"));
|
||||
assertEquals(firstPackageWithLocalVersion, requirementWithLocalVersion.match(Collections.singletonList(firstPackageWithLocalVersion)));
|
||||
assertNull(requirementWithLocalVersion.match(Collections.singletonList(secondPackageWithLocalVersion)));
|
||||
final PyRequirement requirementWithLocalVersion = fromLine("foo==1.0+foo0100");
|
||||
assertEquals(firstPackageWithLocalVersion, requirementWithLocalVersion.match(singletonList(firstPackageWithLocalVersion)));
|
||||
assertNull(requirementWithLocalVersion.match(singletonList(secondPackageWithLocalVersion)));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#version-matching
|
||||
// PY-22275
|
||||
public void testMatchingStar() {
|
||||
final PyRequirement requirement = fix(PyRequirement.fromLine("foo==1.1.*"));
|
||||
final PyPackage release = new PyPackage("foo", "1.1.2", null, Collections.emptyList());
|
||||
final PyPackage pre = new PyPackage("foo", "1.1.2a1", null, Collections.emptyList());
|
||||
final PyPackage post = new PyPackage("foo", "1.1.2.post1", null, Collections.emptyList());
|
||||
final PyPackage dev = new PyPackage("foo", "1.1.2.dev1", null, Collections.emptyList());
|
||||
final PyPackage localVersion = new PyPackage("foo", "1.1.2+local.version", null, Collections.emptyList());
|
||||
final PyRequirement requirement = fromLine("foo==1.1.*");
|
||||
final PyPackage release = new PyPackage("foo", "1.1.2", null, emptyList());
|
||||
final PyPackage pre = new PyPackage("foo", "1.1.2a1", null, emptyList());
|
||||
final PyPackage post = new PyPackage("foo", "1.1.2.post1", null, emptyList());
|
||||
final PyPackage dev = new PyPackage("foo", "1.1.2.dev1", null, emptyList());
|
||||
final PyPackage localVersion = new PyPackage("foo", "1.1.2+local.version", null, emptyList());
|
||||
|
||||
assertEquals(release, requirement.match(Collections.singletonList(release)));
|
||||
assertEquals(pre, requirement.match(Collections.singletonList(pre)));
|
||||
assertEquals(post, requirement.match(Collections.singletonList(post)));
|
||||
assertEquals(dev, requirement.match(Collections.singletonList(dev)));
|
||||
assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion)));
|
||||
assertEquals(release, requirement.match(singletonList(release)));
|
||||
assertEquals(pre, requirement.match(singletonList(pre)));
|
||||
assertEquals(post, requirement.match(singletonList(post)));
|
||||
assertEquals(dev, requirement.match(singletonList(dev)));
|
||||
assertEquals(localVersion, requirement.match(singletonList(localVersion)));
|
||||
|
||||
final PyRequirement negativeRequirement = fix(PyRequirement.fromLine("foo!=1.1.*"));
|
||||
final PyPackage negativeRelease = new PyPackage("foo", "1.2.2", null, Collections.emptyList());
|
||||
final PyPackage negativePre = new PyPackage("foo", "1.2.2a1", null, Collections.emptyList());
|
||||
final PyPackage negativePost = new PyPackage("foo", "1.2.2.post1", null, Collections.emptyList());
|
||||
final PyPackage negativeDev = new PyPackage("foo", "1.2.2.dev1", null, Collections.emptyList());
|
||||
final PyPackage negativeLocalVersion = new PyPackage("foo", "1.2.2+local.version", null, Collections.emptyList());
|
||||
final PyRequirement negativeRequirement = fromLine("foo!=1.1.*");
|
||||
final PyPackage negativeRelease = new PyPackage("foo", "1.2.2", null, emptyList());
|
||||
final PyPackage negativePre = new PyPackage("foo", "1.2.2a1", null, emptyList());
|
||||
final PyPackage negativePost = new PyPackage("foo", "1.2.2.post1", null, emptyList());
|
||||
final PyPackage negativeDev = new PyPackage("foo", "1.2.2.dev1", null, emptyList());
|
||||
final PyPackage negativeLocalVersion = new PyPackage("foo", "1.2.2+local.version", null, emptyList());
|
||||
|
||||
assertNull(negativeRequirement.match(Arrays.asList(release, pre, post, dev, localVersion)));
|
||||
assertEquals(negativeRelease, negativeRequirement.match(Collections.singletonList(negativeRelease)));
|
||||
assertEquals(negativePre, negativeRequirement.match(Collections.singletonList(negativePre)));
|
||||
assertEquals(negativePost, negativeRequirement.match(Collections.singletonList(negativePost)));
|
||||
assertEquals(negativeDev, negativeRequirement.match(Collections.singletonList(negativeDev)));
|
||||
assertEquals(negativeLocalVersion, negativeRequirement.match(Collections.singletonList(negativeLocalVersion)));
|
||||
assertEquals(negativeRelease, negativeRequirement.match(singletonList(negativeRelease)));
|
||||
assertEquals(negativePre, negativeRequirement.match(singletonList(negativePre)));
|
||||
assertEquals(negativePost, negativeRequirement.match(singletonList(negativePost)));
|
||||
assertEquals(negativeDev, negativeRequirement.match(singletonList(negativeDev)));
|
||||
assertEquals(negativeLocalVersion, negativeRequirement.match(singletonList(negativeLocalVersion)));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#compatible-release
|
||||
// PY-20522
|
||||
public void testMatchingCompatible() {
|
||||
final PyRequirement requirement = fix(PyRequirement.fromLine("foo~=2.2"));
|
||||
final PyPackage release = new PyPackage("foo", "2.3", null, Collections.emptyList());
|
||||
final PyPackage pre = new PyPackage("foo", "2.3a1", null, Collections.emptyList());
|
||||
final PyPackage post = new PyPackage("foo", "2.3.post1", null, Collections.emptyList());
|
||||
final PyPackage dev = new PyPackage("foo", "2.3.dev1", null, Collections.emptyList());
|
||||
final PyPackage localVersion = new PyPackage("foo", "2.3+local.version", null, Collections.emptyList());
|
||||
final PyRequirement requirement = fromLine("foo~=2.2");
|
||||
final PyPackage release = new PyPackage("foo", "2.3", null, emptyList());
|
||||
final PyPackage pre = new PyPackage("foo", "2.3a1", null, emptyList());
|
||||
final PyPackage post = new PyPackage("foo", "2.3.post1", null, emptyList());
|
||||
final PyPackage dev = new PyPackage("foo", "2.3.dev1", null, emptyList());
|
||||
final PyPackage localVersion = new PyPackage("foo", "2.3+local.version", null, emptyList());
|
||||
|
||||
assertEquals(release, requirement.match(Collections.singletonList(release)));
|
||||
assertEquals(pre, requirement.match(Collections.singletonList(pre)));
|
||||
assertEquals(post, requirement.match(Collections.singletonList(post)));
|
||||
assertEquals(dev, requirement.match(Collections.singletonList(dev)));
|
||||
assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion)));
|
||||
assertEquals(release, requirement.match(singletonList(release)));
|
||||
assertEquals(pre, requirement.match(singletonList(pre)));
|
||||
assertEquals(post, requirement.match(singletonList(post)));
|
||||
assertEquals(dev, requirement.match(singletonList(dev)));
|
||||
assertEquals(localVersion, requirement.match(singletonList(localVersion)));
|
||||
|
||||
final PyRequirement moreModernRequirement = fix(PyRequirement.fromLine("foo~=2.4"));
|
||||
final PyRequirement moreModernRequirement = fromLine("foo~=2.4");
|
||||
assertNull(moreModernRequirement.match(Arrays.asList(release, pre, post, dev, localVersion)));
|
||||
}
|
||||
|
||||
// https://www.python.org/dev/peps/pep-0440/#compatible-release
|
||||
// PY-20522
|
||||
public void testMatchingCompatibleWithTrailingZero() {
|
||||
final PyRequirement requirement = fix(PyRequirement.fromLine("foo~=2.20.0"));
|
||||
final PyPackage release = new PyPackage("foo", "2.20.3", null, Collections.emptyList());
|
||||
final PyPackage pre = new PyPackage("foo", "2.20.3a1", null, Collections.emptyList());
|
||||
final PyPackage post = new PyPackage("foo", "2.20.3.post1", null, Collections.emptyList());
|
||||
final PyPackage dev = new PyPackage("foo", "2.20.3.dev1", null, Collections.emptyList());
|
||||
final PyPackage localVersion = new PyPackage("foo", "2.20.3+local.version", null, Collections.emptyList());
|
||||
final PyRequirement requirement = fromLine("foo~=2.20.0");
|
||||
final PyPackage release = new PyPackage("foo", "2.20.3", null, emptyList());
|
||||
final PyPackage pre = new PyPackage("foo", "2.20.3a1", null, emptyList());
|
||||
final PyPackage post = new PyPackage("foo", "2.20.3.post1", null, emptyList());
|
||||
final PyPackage dev = new PyPackage("foo", "2.20.3.dev1", null, emptyList());
|
||||
final PyPackage localVersion = new PyPackage("foo", "2.20.3+local.version", null, emptyList());
|
||||
|
||||
assertEquals(release, requirement.match(Collections.singletonList(release)));
|
||||
assertEquals(pre, requirement.match(Collections.singletonList(pre)));
|
||||
assertEquals(post, requirement.match(Collections.singletonList(post)));
|
||||
assertEquals(dev, requirement.match(Collections.singletonList(dev)));
|
||||
assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion)));
|
||||
assertEquals(release, requirement.match(singletonList(release)));
|
||||
assertEquals(pre, requirement.match(singletonList(pre)));
|
||||
assertEquals(post, requirement.match(singletonList(post)));
|
||||
assertEquals(dev, requirement.match(singletonList(dev)));
|
||||
assertEquals(localVersion, requirement.match(singletonList(localVersion)));
|
||||
|
||||
final PyRequirement moreModernRequirement = fix(PyRequirement.fromLine("foo~=2.21.0"));
|
||||
final PyRequirement moreModernRequirement = fromLine("foo~=2.21.0");
|
||||
assertNull(moreModernRequirement.match(Arrays.asList(release, pre, post, dev, localVersion)));
|
||||
}
|
||||
|
||||
// PY-27076
|
||||
public void testMatchingAsteriskAndCompatibleWithTwoTrailingZeros() {
|
||||
final PyRequirement requirement1 = fix(PyRequirement.fromLine("social-auth-app-django==2.0.*"));
|
||||
final PyRequirement requirement2 = fix(PyRequirement.fromLine("social-auth-app-django~=2.0.0"));
|
||||
final PyRequirement requirement1 = fromLine("social-auth-app-django==2.0.*");
|
||||
final PyRequirement requirement2 = fromLine("social-auth-app-django~=2.0.0");
|
||||
|
||||
final PyPackage pkg = new PyPackage("social-auth-app-django", "2.0.0", null, Collections.emptyList());
|
||||
final PyPackage pkg = new PyPackage("social-auth-app-django", "2.0.0", null, emptyList());
|
||||
|
||||
assertEquals(pkg, requirement1.match(Collections.singletonList(pkg)));
|
||||
assertEquals(pkg, requirement2.match(Collections.singletonList(pkg)));
|
||||
assertEquals(pkg, requirement1.match(singletonList(pkg)));
|
||||
assertEquals(pkg, requirement2.match(singletonList(pkg)));
|
||||
}
|
||||
|
||||
// OPTIONS
|
||||
public void testOptions() {
|
||||
assertEmpty(
|
||||
PyRequirement.fromText(
|
||||
PyRequirementParser.fromText(
|
||||
"-i URL\n" +
|
||||
"--index-url URL\n" +
|
||||
"--extra-index-url URL\n" +
|
||||
@@ -2425,13 +2394,13 @@ public class PyRequirementTest extends PyTestCase {
|
||||
final VirtualFile requirementsFile = getVirtualFileByName(getTestDataPath() + "/requirement/recursive/requirements.txt");
|
||||
assertNotNull(requirementsFile);
|
||||
|
||||
assertEquals(Arrays.asList(new PyRequirement("bitly_api"), new PyRequirement("numpy"), new PyRequirement("SomeProject")),
|
||||
PyRequirement.fromFile(requirementsFile));
|
||||
assertEquals(Arrays.asList(pyRequirement("bitly_api"), pyRequirement("numpy"), pyRequirement("SomeProject")),
|
||||
PyRequirementParser.fromFile(requirementsFile));
|
||||
}
|
||||
|
||||
// COMMENTS
|
||||
public void testComment() {
|
||||
assertNull(PyRequirement.fromLine("# comment"));
|
||||
assertNull(fromLine("# comment"));
|
||||
}
|
||||
|
||||
public void testCommentAtTheEnd() {
|
||||
@@ -2463,29 +2432,28 @@ public class PyRequirementTest extends PyTestCase {
|
||||
doCommentAtTheEndTest(name + " # comment");
|
||||
doCommentAtTheEndTest(name, version, name + "==" + version + " # comment");
|
||||
|
||||
assertEquals(new PyRequirement(name, Collections.emptyList(), Collections.singletonList(name + "[PDF]"), "[PDF]"),
|
||||
PyRequirement.fromLine(name + "[PDF] # comment"));
|
||||
assertEquals(new PyRequirementImpl(name, emptyList(), singletonList(name + "[PDF]"), "[PDF]"),
|
||||
fromLine(name + "[PDF] # comment"));
|
||||
|
||||
final PyRequirement requirement = new PyRequirement(name, Collections.emptyList(), Arrays.asList(name, "--install-option", "option"));
|
||||
final PyRequirement requirement = new PyRequirementImpl(name, emptyList(), Arrays.asList(name, "--install-option", "option"), "");
|
||||
|
||||
assertEquals(requirement, PyRequirement.fromLine(name + " --install-option=\"option\" # comment"));
|
||||
assertEquals(Collections.singletonList(requirement),
|
||||
PyRequirement.fromText(name + " \\\n--install-option=\"option\" # comment"));
|
||||
assertEquals(requirement, fromLine(name + " --install-option=\"option\" # comment"));
|
||||
assertEquals(singletonList(requirement), PyRequirementParser.fromText(name + " \\\n--install-option=\"option\" # comment"));
|
||||
}
|
||||
|
||||
// ENV MARKERS
|
||||
// TODO: https://www.python.org/dev/peps/pep-0426/#environment-markers, https://www.python.org/dev/peps/pep-0508/#environment-markers
|
||||
|
||||
private static void doTest(@NotNull String line) {
|
||||
assertEquals(new PyRequirement("MyProject1", Collections.emptyList(), Arrays.asList(line.split("\\s+"))), PyRequirement.fromLine(line));
|
||||
assertEquals(new PyRequirementImpl("MyProject1", emptyList(), Arrays.asList(line.split("\\s+")), ""), fromLine(line));
|
||||
}
|
||||
|
||||
private static void doTest(@NotNull String name, @NotNull String version, @NotNull String line) {
|
||||
assertEquals(new PyRequirement(name, version, Collections.singletonList(line)), PyRequirement.fromLine(line));
|
||||
final PyRequirementVersionSpec versionSpec = pyRequirementVersionSpec(EQ, version);
|
||||
assertEquals(new PyRequirementImpl(name, singletonList(versionSpec), singletonList(line), ""), fromLine(line));
|
||||
}
|
||||
|
||||
private static void doRequirementVersionNormalizationTest(@NotNull String expectedVersion,
|
||||
@NotNull String actualVersion) {
|
||||
private static void doRequirementVersionNormalizationTest(@NotNull String expectedVersion, @NotNull String actualVersion) {
|
||||
final String name = "name";
|
||||
doTest(name, expectedVersion, name + "==" + actualVersion);
|
||||
}
|
||||
@@ -2498,26 +2466,25 @@ public class PyRequirementTest extends PyTestCase {
|
||||
doTest(name, version, line.substring(0, line.lastIndexOf('#') - 1));
|
||||
}
|
||||
|
||||
private static void doRequirementRelationTest(@NotNull PyRequirementRelation relation, @NotNull PyRequirementVersion version) {
|
||||
doRequirementRelationTest("Django", null, Collections.singletonList(relation), Collections.singletonList(version));
|
||||
private static void doRequirementRelationTest(@NotNull PyRequirementRelation relation, @NotNull PyPackageVersion version) {
|
||||
doRequirementRelationTest("Django", null, singletonList(relation), singletonList(version));
|
||||
}
|
||||
|
||||
private static void doRequirementRelationTest(@NotNull List<PyRequirementRelation> relations,
|
||||
@NotNull List<PyRequirementVersion> versions) {
|
||||
private static void doRequirementRelationTest(@NotNull List<PyRequirementRelation> relations, @NotNull List<PyPackageVersion> versions) {
|
||||
doRequirementRelationTest("Django", null, relations, versions);
|
||||
}
|
||||
|
||||
private static void doRequirementRelationTest(@NotNull String name,
|
||||
@Nullable String extras,
|
||||
@NotNull PyRequirementRelation relation,
|
||||
@NotNull PyRequirementVersion version) {
|
||||
doRequirementRelationTest(name, extras, Collections.singletonList(relation), Collections.singletonList(version));
|
||||
@NotNull PyPackageVersion version) {
|
||||
doRequirementRelationTest(name, extras, singletonList(relation), singletonList(version));
|
||||
}
|
||||
|
||||
private static void doRequirementRelationTest(@NotNull String name,
|
||||
@Nullable String extras,
|
||||
@NotNull List<PyRequirementRelation> relations,
|
||||
@NotNull List<PyRequirementVersion> versions) {
|
||||
@NotNull List<PyPackageVersion> versions) {
|
||||
assertEquals(versions.size(), relations.size());
|
||||
|
||||
final StringBuilder sb = new StringBuilder(name);
|
||||
@@ -2525,23 +2492,28 @@ public class PyRequirementTest extends PyTestCase {
|
||||
|
||||
if (extras != null) sb.append(extras);
|
||||
|
||||
for (Pair<PyRequirementRelation, PyRequirementVersion> pair : ContainerUtil.zip(relations, versions)) {
|
||||
for (Pair<PyRequirementRelation, PyPackageVersion> pair : ContainerUtil.zip(relations, versions)) {
|
||||
final PyRequirementRelation relation = pair.getFirst();
|
||||
final PyRequirementVersion version = pair.getSecond();
|
||||
final PyPackageVersion version = pair.getSecond();
|
||||
|
||||
expectedVersionSpecs.add(new PyRequirementVersionSpec(relation, version));
|
||||
expectedVersionSpecs.add(pyRequirementVersionSpec(relation, version));
|
||||
}
|
||||
|
||||
sb.append(StringUtil.join(expectedVersionSpecs, ","));
|
||||
sb.append(StringUtil.join(expectedVersionSpecs, PyRequirementVersionSpec::getPresentableText, ","));
|
||||
|
||||
final String options = sb.toString();
|
||||
|
||||
if (extras == null) {
|
||||
assertEquals(new PyRequirement(name, expectedVersionSpecs, Collections.singletonList(options)), PyRequirement.fromLine(options));
|
||||
assertEquals(new PyRequirementImpl(name, expectedVersionSpecs, singletonList(options), ""), fromLine(options));
|
||||
}
|
||||
else {
|
||||
assertEquals(new PyRequirement(name, expectedVersionSpecs, Collections.singletonList(options), StringUtil.trimLeading(extras)),
|
||||
PyRequirement.fromLine(options));
|
||||
assertEquals(new PyRequirementImpl(name, expectedVersionSpecs, singletonList(options), StringUtil.trimLeading(extras)),
|
||||
fromLine(options));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyPackageVersion release(@NotNull String version) {
|
||||
return new PyPackageVersion(null, version, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user