From ce9b63fd68a77dab8a4f39bcdef3207e92e39328 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 16 Mar 2012 16:41:55 +0400 Subject: [PATCH] Fixed parsing editable package requirements (PY-5929) --- .../python/packaging/PyPackageManager.java | 4 +- .../python/packaging/PyRequirement.java | 134 +++++++++++++++--- 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManager.java b/python/src/com/jetbrains/python/packaging/PyPackageManager.java index f017d819d9b6..d09a21d0fb76 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManager.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManager.java @@ -216,7 +216,7 @@ public class PyPackageManager { } args.addAll(extraArgs); for (PyRequirement req : requirements) { - args.add(req.toString()); + args.addAll(req.toOptions()); } try { runPythonHelper(PACKAGING_TOOL, args); @@ -397,7 +397,7 @@ public class PyPackageManager { final String name = fields.get(0); final String version = fields.get(1); final String location = fields.get(2); - if (!"Python".equals(name) && !"wsgiref".equals(name)) { + if (!"Python".equals(name)) { packages.add(new PyPackage(name, version, location, new ArrayList())); } } diff --git a/python/src/com/jetbrains/python/packaging/PyRequirement.java b/python/src/com/jetbrains/python/packaging/PyRequirement.java index 20cf98f45d98..526e68485879 100644 --- a/python/src/com/jetbrains/python/packaging/PyRequirement.java +++ b/python/src/com/jetbrains/python/packaging/PyRequirement.java @@ -1,14 +1,12 @@ package com.jetbrains.python.packaging; +import com.google.common.collect.ImmutableMap; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -16,8 +14,10 @@ import java.util.regex.Pattern; * @author vlan */ public class PyRequirement { - private static final Pattern NAME = Pattern.compile("\\s*((\\w|[-.])+)\\s*(.*)"); - private static final Pattern VERSION = Pattern.compile("\\s*(<=?|>=?|==|!=)\\s*((\\w|[-.])+)"); + private static final Pattern NAME = Pattern.compile("\\s*(\\w(\\w|[-.])*)\\s*(.*)"); + private static final Pattern VERSION_SPEC = Pattern.compile("\\s*(<=?|>=?|==|!=)\\s*((\\w|[-.])+)"); + private static final Pattern EDITABLE_EGG = Pattern.compile("\\s*-e\\s+([^#]*)#egg=(.*)"); + private static final Pattern NAME_VERSION = Pattern.compile("\\s*(\\w(\\w|[.])*)-((\\w|[-.])+)"); public enum Relation { LT("<"), @@ -112,6 +112,7 @@ public class PyRequirement { @NotNull private final String myName; @NotNull private final List myVersionSpecs; + @Nullable private final String myURL; public static final Comparator VERSION_COMPARATOR = new Comparator() { @Override @@ -131,10 +132,41 @@ public class PyRequirement { return result; } + @Nullable + private String replace(@NotNull String s) { + final Map sub = ImmutableMap.of("pre", "c", + "preview", "c", + "rc", "c", + "dev", "@"); + final String tmp = sub.get(s); + if (tmp != null) { + s = tmp; + } + if (s.equals(".") || s.equals("-")) { + return null; + } + if (s.matches("[0-9]+")) { + final int value = Integer.parseInt(s); + return String.format("%08d", value); + } + return "*" + s; + } + @NotNull private List parse(@Nullable String s) { - // TODO: Take version modificators (dev, alpha, beta, b, etc.) into account, see pkg_resources parsing for ideas - return s != null ? StringUtil.split(s, ".") : Collections.emptyList(); + // Version parsing from pkg_resources ensures that all the "pre", "alpha", "rc", etc. are sorted correctly + final Pattern COMPONENT_RE = Pattern.compile("\\d+|[a-z]+|\\.|-|.+"); + final List results = new ArrayList(); + final Matcher matcher = COMPONENT_RE.matcher(s); + while (matcher.find()) { + final String component = replace(matcher.group()); + if (component == null) { + continue; + } + results.add(component); + } + results.add("*final"); + return results; } }; @@ -149,6 +181,18 @@ public class PyRequirement { public PyRequirement(@NotNull String name, @NotNull List versionSpecs) { myName = name; myVersionSpecs = versionSpecs; + myURL = null; + } + + public PyRequirement(@NotNull String name, @Nullable String version, @NotNull String url) { + myName = name; + if (version != null) { + myVersionSpecs = Collections.singletonList(new VersionSpec(Relation.GTE, version)); + } + else { + myVersionSpecs = Collections.emptyList(); + } + myURL = url; } @NotNull @@ -164,6 +208,29 @@ public class PyRequirement { ","); } + @NotNull + public List toOptions() { + if (myURL != null) { + final int size = myVersionSpecs.size(); + assert size <= 1; + final List results = new ArrayList(); + results.add("-e"); + final String urlAndName = myURL + "#egg=" + myName; + if (size == 0) { + results.add(urlAndName); + } + else { + final VersionSpec versionSpec = myVersionSpecs.get(0); + assert versionSpec.getRelation() == Relation.EQ; + results.add(urlAndName + "-" + versionSpec.getVersion()); + } + return results; + } + else { + return Collections.singletonList(toString()); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -200,6 +267,10 @@ public class PyRequirement { @Nullable public static PyRequirement fromString(@NotNull String s) { // TODO: Extras, multi-line requirements '\' + final PyRequirement editableEgg = parseEditableEgg(s); + if (editableEgg != null) { + return editableEgg; + } final Matcher nameMatcher = NAME.matcher(s); if (!nameMatcher.matches()) { return null; @@ -208,10 +279,10 @@ public class PyRequirement { final String rest = nameMatcher.group(3); final List versionSpecs = new ArrayList(); if (!rest.trim().isEmpty()) { - final Matcher versionMatcher = VERSION.matcher(rest); - while (versionMatcher.find()) { - final String rel = versionMatcher.group(1); - final String version = versionMatcher.group(2); + final Matcher versionSpecMatcher = VERSION_SPEC.matcher(rest); + while (versionSpecMatcher.find()) { + final String rel = versionSpecMatcher.group(1); + final String version = versionSpecMatcher.group(2); final Relation relation = Relation.fromString(rel); if (relation == null) { return null; @@ -222,7 +293,7 @@ public class PyRequirement { return new PyRequirement(name, versionSpecs); } - @Nullable + @NotNull public static List parse(@NotNull String s) { final List result = new ArrayList(); for (String line : StringUtil.splitByLines(s)) { @@ -232,9 +303,6 @@ public class PyRequirement { if (req != null) { result.add(req); } - else { - return null; - } } } return result; @@ -244,4 +312,38 @@ public class PyRequirement { public String getName() { return myName; } + + @Nullable + private static PyRequirement parseEditableEgg(@NotNull String s) { + final Matcher editableEggMatcher = EDITABLE_EGG.matcher(s); + if (!editableEggMatcher.matches()) { + return null; + } + final String url = editableEggMatcher.group(1); + final String egg = editableEggMatcher.group(2); + final Matcher nameVersionMatcher = NAME_VERSION.matcher(egg); + if (nameVersionMatcher.matches()) { + final String name = normalizeName(nameVersionMatcher.group(1)); + final String version = normalizeVersion(nameVersionMatcher.group(3)); + return new PyRequirement(name, version, url); + } + else { + final Matcher nameMatcher = NAME.matcher(egg); + if (!nameMatcher.matches()) { + return null; + } + final String name = normalizeName(nameMatcher.group(1)); + return new PyRequirement(name, null, url); + } + } + + @NotNull + private static String normalizeName(@NotNull String s) { + return s.replace("_", "-"); + } + + @NotNull + private static String normalizeVersion(@NotNull String s) { + return s.replace("_", "-").replaceAll("-?py[0-9\\.]+", ""); + } }