Repository: conda/conda-verify

Issue:
Pre-release versioning throws C1114
Prerelease versioning such as 2.0.0a0 will throw C1114 even though it's an acceptable versioning format.

Discussion:

Patch:
diff --git a/conda_verify/checks.py b/conda_verify/checks.py
index 1d8c170..a059c14 100644
--- a/conda_verify/checks.py
+++ b/conda_verify/checks.py
@@ -21,6 +21,8 @@ from conda_verify.utilities import (all_ascii, get_bad_seq, get_object_type,
                                     ensure_list, fullmatch)
 
 
+ver_spec_pat = '^(?:[><=]{0,2}(?:(?:[\d\*]+[!\._]?){1,})[+\w\*]*[|,]?){1,}'
+
 class CondaPackageCheck(object):
     """Create checks in order to validate conda package tarballs."""
 
@@ -40,7 +42,6 @@ class CondaPackageCheck(object):
         self.name_pat = re.compile(r'[a-z0-9_][a-z0-9_\-\.]*$')
         self.hash_pat = re.compile(r'[gh][0-9a-f]{5,}', re.I)
         self.version_pat = re.compile(r'[\w\.]+$')
-        self.ver_spec_pat = '^((==|>=|<=|<|>)(\d+(\.\d+)*(\.)*\*?)\,(==|>=|<=|<|>)(\d+(\.\d+)*(\.)*\*?)|(==|>=|<=|<|>)(\d+(\.\d+)*(\.)*\*?)|(\d+(\.\d+)*(\.)*\*?)\|(\d+(\.\d+)*(\.)*\*?)|(\d+(\.\d+)*(\.)*\*?))'
 
     @staticmethod
     def retrieve_package_name(path):
@@ -115,7 +116,8 @@ class CondaPackageCheck(object):
                 dependency_parts = dependency.split()
                 if len(dependency_parts) == 0:
                     return Error(self.path, 'C1113', 'Found empty dependencies in info/index.json')
-                elif len(dependency_parts) == 2 and not fullmatch(self.ver_spec_pat, dependency_parts[1]) or len(dependency_parts) > 3:
+                elif len(dependency_parts) == 2 and not fullmatch(ver_spec_pat,
+                                                    dependency_parts[1]) or len(dependency_parts) > 3:
                     return Error(self.path, 'C1114', 'Found invalid dependency "{}" in info/index.json' .format(dependency))
 
     def check_license_family(self):
@@ -376,7 +378,6 @@ class CondaRecipeCheck(object):
         self.recipe_dir = recipe_dir
         self.name_pat = re.compile(r'[a-z0-9_][a-z0-9_\-\.]*$')
         self.version_pat = re.compile(r'[\w\.]+$')
-        self.ver_spec_pat = '^((==|>=|<=|<|>)(\d+(\.\d+)*(\.)*\*?)\,(==|>=|<=|<|>)(\d+(\.\d+)*(\.)*\*?)|(==|>=|<=|<|>)(\d+(\.\d+)*(\.)*\*?)|(\d+(\.\d+)*(\.)*\*?)\|(\d+(\.\d+)*(\.)*\*?)|(\d+(\.\d+)*(\.)*\*?))'
         self.url_pat = re.compile(r'(ftp|http(s)?)://')
         self.hash_pat = {'md5': re.compile(r'[a-f0-9]{32}$'),
                          'sha1': re.compile(r'[a-f0-9]{40}$'),
@@ -461,7 +462,7 @@ class CondaRecipeCheck(object):
             if len(requirement_parts) == 0:
                 return Error(self.recipe_dir, 'C2113', 'Found empty dependencies in meta.yaml')
 
-            elif len(requirement_parts) >= 2 and not fullmatch(self.ver_spec_pat, requirement_parts[1]):
+            elif len(requirement_parts) >= 2 and not fullmatch(ver_spec_pat, requirement_parts[1]):
                 return Error(self.recipe_dir, 'C2114', u'Found invalid dependency "{}" in meta.yaml' .format(requirement))
 
         if len(build_requirements) != len(set(build_requirements)):


Test patch:
diff --git a/tests/unit_tests/test_regex.py b/tests/unit_tests/test_regex.py
index 5f47f3c..bdfba76 100644
--- a/tests/unit_tests/test_regex.py
+++ b/tests/unit_tests/test_regex.py
@@ -16,19 +16,16 @@ def recipe_dir():
 
 
 def test_ver_spec_pat(package_dir, recipe_dir):
-    package = os.path.join(package_dir, 'testfile-0.0.30-py27_0.tar.bz2')
-    recipe = os.path.join(recipe_dir, 'valid_test_file')
-    metadata = utilities.render_metadata(recipe, None)
-    package_check = checks.CondaPackageCheck(package)
-    recipe_check = checks.CondaRecipeCheck(metadata, recipe)
-    package_ver_spec_pat = package_check.ver_spec_pat
-    recipe_ver_spec_pat = recipe_check.ver_spec_pat
+    package_ver_spec_pat = checks.ver_spec_pat
+    recipe_ver_spec_pat = checks.ver_spec_pat
 
     extra_spec = '>===3.5'
     ge_version = '>=1.2'
     eq_version = '==1.2.2'
     pin_version = '>=2,<3'
     pin_version_long = '<=2.0.0*,<3.0.0*'
+    prerelease_version = '2.0rc1'
+    pin_prerelease_version = '>=1.9.3,<2.0.0a0'
     or_version = '1.0|1.2.*'
     regex_version = '3.6*'
     python_version = '3.6.*'
@@ -38,6 +35,8 @@ def test_ver_spec_pat(package_dir, recipe_dir):
     assert utilities.fullmatch(package_ver_spec_pat, eq_version)
     assert utilities.fullmatch(package_ver_spec_pat, pin_version)
     assert utilities.fullmatch(package_ver_spec_pat, pin_version_long)
+    assert utilities.fullmatch(package_ver_spec_pat, prerelease_version)
+    assert utilities.fullmatch(package_ver_spec_pat, pin_prerelease_version)
     assert utilities.fullmatch(package_ver_spec_pat, or_version)
     assert utilities.fullmatch(package_ver_spec_pat, regex_version)
     assert utilities.fullmatch(package_ver_spec_pat, python_version)
@@ -47,6 +46,8 @@ def test_ver_spec_pat(package_dir, recipe_dir):
     assert utilities.fullmatch(recipe_ver_spec_pat, eq_version)
     assert utilities.fullmatch(recipe_ver_spec_pat, pin_version)
     assert utilities.fullmatch(recipe_ver_spec_pat, pin_version_long)
+    assert utilities.fullmatch(recipe_ver_spec_pat, prerelease_version)
+    assert utilities.fullmatch(recipe_ver_spec_pat, pin_prerelease_version)
     assert utilities.fullmatch(recipe_ver_spec_pat, or_version)
     assert utilities.fullmatch(recipe_ver_spec_pat, regex_version)
     assert utilities.fullmatch(recipe_ver_spec_pat, python_version)