[Python-modules-commits] [python-cssselect] 01/03: Import python-cssselect_1.0.0.orig.tar.gz

Wolfgang Borgert debacle at moszumanska.debian.org
Thu Dec 8 08:44:30 UTC 2016


This is an automated email from the git hooks/post-receive script.

debacle pushed a commit to branch master
in repository python-cssselect.

commit 80923de732e6623088c6aa08052a5917c37faed8
Author: W. Martin Borgert <debacle at debian.org>
Date:   Thu Dec 8 00:05:57 2016 +0000

    Import python-cssselect_1.0.0.orig.tar.gz
---
 .bumpversion.cfg                              |   7 ++
 .coveragerc                                   |   1 +
 .gitignore                                    |   7 ++
 .travis.yml                                   |  22 ++++
 CHANGES                                       |  10 ++
 PKG-INFO                                      |  46 -------
 cssselect.egg-info/PKG-INFO                   |  46 -------
 cssselect.egg-info/SOURCES.txt                |  19 ---
 cssselect.egg-info/dependency_links.txt       |   1 -
 cssselect.egg-info/top_level.txt              |   1 -
 cssselect/__init__.py                         |   2 +-
 cssselect/xpath.py                            | 167 +++++++++++++++++++-------
 docs/conf.py                                  |   2 +-
 setup.cfg                                     |  13 +-
 tests/__init__.py                             |   0
 cssselect/tests.py => tests/test_cssselect.py | 116 ++++++++++++++----
 tox.ini                                       |   9 +-
 17 files changed, 276 insertions(+), 193 deletions(-)

diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644
index 0000000..426ea28
--- /dev/null
+++ b/.bumpversion.cfg
@@ -0,0 +1,7 @@
+[bumpversion]
+current_version = 1.0.0
+commit = True
+tag = True
+
+[bumpversion:file:cssselect/__init__.py]
+
diff --git a/.coveragerc b/.coveragerc
index 2ee5ff3..ed1fac6 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,5 +1,6 @@
 [run]
 branch = True
+source = cssselect
 
 [report]
 exclude_lines =
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c89f4c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.pyc
+*.egg-info
+/.tox
+/MANIFEST
+/dist
+/docs/_build
+/.coverage
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a89d5b3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+language: python
+
+python:
+  - "2.6"
+  - "2.7"
+  - "3.2"
+  - "3.3"
+  - "3.4"
+  - "3.5"
+
+install:
+  - pip install lxml -e .
+  - pip install -U codecov pytest-cov
+  - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]];
+    then pip uninstall -y coverage pytest && pip install "coverage<4" && pip install "pytest<3";
+    fi
+
+script:
+  py.test --cov-report term --cov=cssselect
+
+after_success:
+  codecov
diff --git a/CHANGES b/CHANGES
index 5ae9a39..94abe77 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,16 @@
 Changelog
 =========
 
+Version 1.0.0
+-------------
+
+Released on 2016-10-21.
+
+* Add code coverage reports.
+* Fix ``:nth-*(an+b)`` pseudo-classes selectors.
+  (except ``*:nth-child()`` which looks untranslatable to XPath 1.0.)
+
+
 Version 0.9.2
 -------------
 
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index ecdb2a4..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,46 +0,0 @@
-Metadata-Version: 1.1
-Name: cssselect
-Version: 0.9.2
-Summary: cssselect parses CSS3 Selectors and translates them to XPath 1.0
-Home-page: https://pythonhosted.org/cssselect/
-Author: Paul Tremberth
-Author-email: paul.tremberth at gmail.com
-License: BSD
-Description: ===================================
-        cssselect: CSS Selectors for Python
-        ===================================
-        
-        *cssselect* parses `CSS3 Selectors`_ and translate them to `XPath 1.0`_
-        expressions. Such expressions can be used in lxml_ or another XPath engine
-        to find the matching elements in an XML or HTML document.
-        
-        This module used to live inside of lxml as ``lxml.cssselect`` before it was
-        extracted as a stand-alone project.
-        
-        .. _CSS3 Selectors: https://www.w3.org/TR/css3-selectors/
-        .. _XPath 1.0: https://www.w3.org/TR/xpath/
-        .. _lxml: http://lxml.de/
-        
-        
-        Quick facts:
-        
-        * Free software: BSD licensed
-        * Compatible with Python 2.6+ and 3.2+
-        * Latest documentation `on python.org <https://pythonhosted.org/cssselect/>`_
-        * Source, issues and pull requests `on Github
-          <https://github.com/scrapy/cssselect>`_
-        * Releases `on PyPI <http://pypi.python.org/pypi/cssselect>`_
-        * Install with ``pip install cssselect``
-        
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
diff --git a/cssselect.egg-info/PKG-INFO b/cssselect.egg-info/PKG-INFO
deleted file mode 100644
index ecdb2a4..0000000
--- a/cssselect.egg-info/PKG-INFO
+++ /dev/null
@@ -1,46 +0,0 @@
-Metadata-Version: 1.1
-Name: cssselect
-Version: 0.9.2
-Summary: cssselect parses CSS3 Selectors and translates them to XPath 1.0
-Home-page: https://pythonhosted.org/cssselect/
-Author: Paul Tremberth
-Author-email: paul.tremberth at gmail.com
-License: BSD
-Description: ===================================
-        cssselect: CSS Selectors for Python
-        ===================================
-        
-        *cssselect* parses `CSS3 Selectors`_ and translate them to `XPath 1.0`_
-        expressions. Such expressions can be used in lxml_ or another XPath engine
-        to find the matching elements in an XML or HTML document.
-        
-        This module used to live inside of lxml as ``lxml.cssselect`` before it was
-        extracted as a stand-alone project.
-        
-        .. _CSS3 Selectors: https://www.w3.org/TR/css3-selectors/
-        .. _XPath 1.0: https://www.w3.org/TR/xpath/
-        .. _lxml: http://lxml.de/
-        
-        
-        Quick facts:
-        
-        * Free software: BSD licensed
-        * Compatible with Python 2.6+ and 3.2+
-        * Latest documentation `on python.org <https://pythonhosted.org/cssselect/>`_
-        * Source, issues and pull requests `on Github
-          <https://github.com/scrapy/cssselect>`_
-        * Releases `on PyPI <http://pypi.python.org/pypi/cssselect>`_
-        * Install with ``pip install cssselect``
-        
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
diff --git a/cssselect.egg-info/SOURCES.txt b/cssselect.egg-info/SOURCES.txt
deleted file mode 100644
index c1037d3..0000000
--- a/cssselect.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-.coveragerc
-AUTHORS
-CHANGES
-LICENSE
-MANIFEST.in
-README.rst
-setup.cfg
-setup.py
-tox.ini
-cssselect/__init__.py
-cssselect/parser.py
-cssselect/tests.py
-cssselect/xpath.py
-cssselect.egg-info/PKG-INFO
-cssselect.egg-info/SOURCES.txt
-cssselect.egg-info/dependency_links.txt
-cssselect.egg-info/top_level.txt
-docs/conf.py
-docs/index.rst
\ No newline at end of file
diff --git a/cssselect.egg-info/dependency_links.txt b/cssselect.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/cssselect.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/cssselect.egg-info/top_level.txt b/cssselect.egg-info/top_level.txt
deleted file mode 100644
index d2a154e..0000000
--- a/cssselect.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-cssselect
diff --git a/cssselect/__init__.py b/cssselect/__init__.py
index ed330ac..f46a0e4 100644
--- a/cssselect/__init__.py
+++ b/cssselect/__init__.py
@@ -18,5 +18,5 @@ from cssselect.parser import (parse, Selector, FunctionalPseudoElement,
 from cssselect.xpath import GenericTranslator, HTMLTranslator, ExpressionError
 
 
-VERSION = '0.9.2'
+VERSION = '1.0.0'
 __version__ = VERSION
diff --git a/cssselect/xpath.py b/cssselect/xpath.py
index b0913ab..698748a 100644
--- a/cssselect/xpath.py
+++ b/cssselect/xpath.py
@@ -376,40 +376,127 @@ class GenericTranslator(object):
             a, b = parse_series(function.arguments)
         except ValueError:
             raise ExpressionError("Invalid series: '%r'" % function.arguments)
+
+        # From https://www.w3.org/TR/css3-selectors/#structural-pseudos:
+        #
+        # :nth-child(an+b)
+        #       an+b-1 siblings before
+        #
+        # :nth-last-child(an+b)
+        #       an+b-1 siblings after
+        #
+        # :nth-of-type(an+b)
+        #       an+b-1 siblings with the same expanded element name before
+        #
+        # :nth-last-of-type(an+b)
+        #       an+b-1 siblings with the same expanded element name after
+        #
+        # So,
+        # for :nth-child and :nth-of-type
+        #
+        #    count(preceding-sibling::<nodetest>) = an+b-1
+        #
+        # for :nth-last-child and :nth-last-of-type
+        #
+        #    count(following-sibling::<nodetest>) = an+b-1
+        #
+        # therefore,
+        #    count(...) - (b-1) ≡ 0 (mod a)
+        #
+        # if a == 0:
+        # ~~~~~~~~~~
+        #    count(...) = b-1
+        #
+        # if a < 0:
+        # ~~~~~~~~~
+        #    count(...) - b +1 <= 0
+        # -> count(...) <= b-1
+        #
+        # if a > 0:
+        # ~~~~~~~~~
+        #    count(...) - b +1 >= 0
+        # -> count(...) >= b-1
+
+        # work with b-1 instead
+        b_min_1 = b - 1
+
+        # early-exit condition 1:
+        # ~~~~~~~~~~~~~~~~~~~~~~~
+        # for a == 1, nth-*(an+b) means n+b-1 siblings before/after,
+        # and since n ∈ {0, 1, 2, ...}, if b-1<=0,
+        # there is always an "n" matching any number of siblings (maybe none)
+        if a == 1 and b_min_1 <=0:
+            return xpath
+
+        # early-exit condition 2:
+        # ~~~~~~~~~~~~~~~~~~~~~~~
+        # an+b-1 siblings with a<0 and (b-1)<0 is not possible
+        if a < 0 and b_min_1 < 0:
+            return xpath.add_condition('0')
+
+        # `add_name_test` boolean is inverted and somewhat counter-intuitive:
+        #
+        # nth_of_type() calls nth_child(add_name_test=False)
         if add_name_test:
-            xpath.add_name_test()
-        xpath.add_star_prefix()
-        if a == 0:
-            if last:
-                b = 'last() - %s' % b
-            return xpath.add_condition('position() = %s' % b)
-        if last:
-            # FIXME: I'm not sure if this is right
-            a = -a
-            b = -b
-        if b > 0:
-            b_neg = str(-b)
+            nodetest = '*'
+        else:
+            nodetest  = '%s' % xpath.element
+
+        # count siblings before or after the element
+        if not last:
+            siblings_count = 'count(preceding-sibling::%s)' % nodetest
         else:
-            b_neg = '+%s' % (-b)
-        if a != 1:
-            expr = ['(position() %s) mod %s = 0' % (b_neg, a)]
+            siblings_count = 'count(following-sibling::%s)' % nodetest
+
+        # special case of fixed position: nth-*(0n+b)
+        # if a == 0:
+        # ~~~~~~~~~~
+        #    count(***-sibling::***) = b-1
+        if a == 0:
+            return xpath.add_condition('%s = %s' % (siblings_count, b_min_1))
+
+        expr = []
+
+        if a > 0:
+            # siblings count, an+b-1, is always >= 0,
+            # so if a>0, and (b-1)<=0, an "n" exists to satisfy this,
+            # therefore, the predicate is only interesting if (b-1)>0
+            if b_min_1 > 0:
+                expr.append('%s >= %s' % (siblings_count, b_min_1))
         else:
-            expr = []
-        if b >= 0:
-            expr.append('position() >= %s' % b)
-        elif b < 0 and last:
-            expr.append('position() < (last() %s)' % b)
-        expr = ' and '.join(expr)
-        if expr:
-            xpath.add_condition(expr)
+            # if a<0, and (b-1)<0, no "n" satisfies this,
+            # this is tested above as an early exist condition
+            # otherwise,
+            expr.append('%s <= %s' % (siblings_count, b_min_1))
+
+        # operations modulo 1 or -1 are simpler, one only needs to verify:
+        #
+        # - either:
+        # count(***-sibling::***) - (b-1) = n = 0, 1, 2, 3, etc.,
+        #   i.e. count(***-sibling::***) >= (b-1)
+        #
+        # - or:
+        # count(***-sibling::***) - (b-1) = -n = 0, -1, -2, -3, etc.,
+        #   i.e. count(***-sibling::***) <= (b-1)
+        # we we just did above.
+        #
+        if abs(a) != 1:
+            # count(***-sibling::***) - (b-1) ≡ 0 (mod a)
+            left = siblings_count
+
+            # apply "modulo a" on 2nd term, -(b-1),
+            # to simplify things like "(... +6) % -3",
+            # and also make it positive with |a|
+            b_neg = (-b_min_1) % abs(a)
+
+            if b_neg != 0:
+                b_neg = '+%s' % (b_neg)
+                left = '(%s %s)' % (left, b_neg)
+
+            expr.append('%s mod %s = 0' % (left, a))
+
+        xpath.add_condition(' and '.join(expr))
         return xpath
-        # FIXME: handle an+b, odd, even
-        # an+b means every-a, plus b, e.g., 2n+1 means odd
-        # 0n+b means b
-        # n+0 means a=1, i.e., all elements
-        # an means every a elements, i.e., 2n means even
-        # -n means -1n
-        # -1n+6 means elements 6 and previous
 
     def xpath_nth_last_child_function(self, xpath, function):
         return self.xpath_nth_child_function(xpath, function, last=True)
@@ -455,39 +542,31 @@ class GenericTranslator(object):
         return xpath.add_condition("not(parent::*)")
 
     def xpath_first_child_pseudo(self, xpath):
-        xpath.add_star_prefix()
-        xpath.add_name_test()
-        return xpath.add_condition('position() = 1')
+        return xpath.add_condition('count(preceding-sibling::*) = 0')
 
     def xpath_last_child_pseudo(self, xpath):
-        xpath.add_star_prefix()
-        xpath.add_name_test()
-        return xpath.add_condition('position() = last()')
+        return xpath.add_condition('count(following-sibling::*) = 0')
 
     def xpath_first_of_type_pseudo(self, xpath):
         if xpath.element == '*':
             raise ExpressionError(
                 "*:first-of-type is not implemented")
-        xpath.add_star_prefix()
-        return xpath.add_condition('position() = 1')
+        return xpath.add_condition('count(preceding-sibling::%s) = 0' % xpath.element)
 
     def xpath_last_of_type_pseudo(self, xpath):
         if xpath.element == '*':
             raise ExpressionError(
                 "*:last-of-type is not implemented")
-        xpath.add_star_prefix()
-        return xpath.add_condition('position() = last()')
+        return xpath.add_condition('count(following-sibling::%s) = 0' % xpath.element)
 
     def xpath_only_child_pseudo(self, xpath):
-        xpath.add_name_test()
-        xpath.add_star_prefix()
-        return xpath.add_condition('last() = 1')
+        return xpath.add_condition('count(parent::*/child::*) = 1')
 
     def xpath_only_of_type_pseudo(self, xpath):
         if xpath.element == '*':
             raise ExpressionError(
                 "*:only-of-type is not implemented")
-        return xpath.add_condition('last() = 1')
+        return xpath.add_condition('count(parent::*/child::%s) = 1' % xpath.element)
 
     def xpath_empty_pseudo(self, xpath):
         return xpath.add_condition("not(*) and not(string-length())")
diff --git a/docs/conf.py b/docs/conf.py
index 22e6032..b2612d0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -95,7 +95,7 @@ pygments_style = 'sphinx'
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-#html_theme = 'agogo'
+html_theme = 'classic'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
diff --git a/setup.cfg b/setup.cfg
index 0ab3578..270daee 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,18 +1,13 @@
 [build_sphinx]
 source-dir = docs
-build-dir = docs/_build
+build-dir  = docs/_build
+#all_files  = 1
 
-[upload_sphinx]
+[upload_sphinx] # Sphinx-PyPI-upload
 upload-dir = docs/_build/html
 
 [pytest]
-python_files = tests.py
+testpaths = tests
 
 [bdist_wheel]
 universal = 1
-
-[egg_info]
-tag_build = 
-tag_date = 0
-tag_svn_revision = 0
-
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/cssselect/tests.py b/tests/test_cssselect.py
old mode 100755
new mode 100644
similarity index 92%
rename from cssselect/tests.py
rename to tests/test_cssselect.py
index 567e3c5..4a0bd39
--- a/cssselect/tests.py
+++ b/tests/test_cssselect.py
@@ -334,35 +334,73 @@ class TestCssselect(unittest.TestCase):
         assert xpath('e[hreflang|="en"]') == (
             "e[@hreflang and ("
                "@hreflang = 'en' or starts-with(@hreflang, 'en-'))]")
+
+        # --- nth-* and nth-last-* -------------------------------------
         assert xpath('e:nth-child(1)') == (
-            "*/*[name() = 'e' and (position() = 1)]")
+            "e[count(preceding-sibling::*) = 0]")
+
+        # always true
+        assert xpath('e:nth-child(n)') == (
+            "e")
+        assert xpath('e:nth-child(n+1)') == (
+            "e")
+        # always true too
+        assert xpath('e:nth-child(n-10)') == (
+            "e")
+        # b=2 is the limit...
+        assert xpath('e:nth-child(n+2)') == (
+            "e[count(preceding-sibling::*) >= 1]")
+        # always false
+        assert xpath('e:nth-child(-n)') == (
+            "e[0]")
+        # equivalent to first child
+        assert xpath('e:nth-child(-n+1)') == (
+            "e[count(preceding-sibling::*) <= 0]")
+
+        assert xpath('e:nth-child(3n+2)') == (
+            "e[count(preceding-sibling::*) >= 1 and "
+              "(count(preceding-sibling::*) +2) mod 3 = 0]")
+        assert xpath('e:nth-child(3n-2)') == (
+            "e[count(preceding-sibling::*) mod 3 = 0]")
+        assert xpath('e:nth-child(-n+6)') == (
+            "e[count(preceding-sibling::*) <= 5]")
+
         assert xpath('e:nth-last-child(1)') == (
-            "*/*[name() = 'e' and (position() = last() - 1)]")
+            "e[count(following-sibling::*) = 0]")
+        assert xpath('e:nth-last-child(2n)') == (
+            "e[(count(following-sibling::*) +1) mod 2 = 0]")
+        assert xpath('e:nth-last-child(2n+1)') == (
+            "e[count(following-sibling::*) mod 2 = 0]")
         assert xpath('e:nth-last-child(2n+2)') == (
-            "*/*[name() = 'e' and ("
-               "(position() +2) mod -2 = 0 and position() < (last() -2))]")
+            "e[count(following-sibling::*) >= 1 and "
+              "(count(following-sibling::*) +1) mod 2 = 0]")
+        assert xpath('e:nth-last-child(3n+1)') == (
+            "e[count(following-sibling::*) mod 3 = 0]")
+        # represents the two last e elements
+        assert xpath('e:nth-last-child(-n+2)') == (
+            "e[count(following-sibling::*) <= 1]")
+
         assert xpath('e:nth-of-type(1)') == (
-            "*/e[position() = 1]")
-        assert xpath('e:nth-last-of-type(1)') == (
-            "*/e[position() = last() - 1]")
+            "e[count(preceding-sibling::e) = 0]")
         assert xpath('e:nth-last-of-type(1)') == (
-            "*/e[position() = last() - 1]")
+            "e[count(following-sibling::e) = 0]")
         assert xpath('div e:nth-last-of-type(1) .aclass') == (
-            "div/descendant-or-self::*/e[position() = last() - 1]"
+            "div/descendant-or-self::*/e[count(following-sibling::e) = 0]"
                "/descendant-or-self::*/*[@class and contains("
                "concat(' ', normalize-space(@class), ' '), ' aclass ')]")
+
         assert xpath('e:first-child') == (
-            "*/*[name() = 'e' and (position() = 1)]")
+            "e[count(preceding-sibling::*) = 0]")
         assert xpath('e:last-child') == (
-            "*/*[name() = 'e' and (position() = last())]")
+            "e[count(following-sibling::*) = 0]")
         assert xpath('e:first-of-type') == (
-            "*/e[position() = 1]")
+            "e[count(preceding-sibling::e) = 0]")
         assert xpath('e:last-of-type') == (
-            "*/e[position() = last()]")
+            "e[count(following-sibling::e) = 0]")
         assert xpath('e:only-child') == (
-            "*/*[name() = 'e' and (last() = 1)]")
+            "e[count(parent::*/child::*) = 1]")
         assert xpath('e:only-of-type') == (
-            "e[last() = 1]")
+            "e[count(parent::*/child::e) = 1]")
         assert xpath('e:empty') == (
             "e[not(*) and not(string-length())]")
         assert xpath('e:EmPTY') == (
@@ -381,7 +419,7 @@ class TestCssselect(unittest.TestCase):
         assert xpath('e#myid') == (
             "e[@id = 'myid']")
         assert xpath('e:not(:nth-child(odd))') == (
-            "e[not((position() -1) mod 2 = 0 and position() >= 1)]")
+            "e[not(count(preceding-sibling::*) mod 2 = 0)]")
         assert xpath('e:nOT(*)') == (
             "e[0]")  # never matches
         assert xpath('e f') == (
@@ -392,6 +430,8 @@ class TestCssselect(unittest.TestCase):
             "e/following-sibling::*[name() = 'f' and (position() = 1)]")
         assert xpath('e ~ f') == (
             "e/following-sibling::f")
+        assert xpath('e ~ f:nth-child(3)') == (
+            "e/following-sibling::f[count(preceding-sibling::*) = 2]")
         assert xpath('div#container p') == (
             "div[@id = 'container']/descendant-or-self::*/p")
 
@@ -632,7 +672,18 @@ class TestCssselect(unittest.TestCase):
         assert pcss(':lang("EN")', '*:lang(en-US)', html_only=True) == [
             'second-li', 'li-div']
         assert pcss(':lang("e")', html_only=True) == []
-        assert pcss('li:nth-child(3)') == ['third-li']
+
+        # --- nth-* and nth-last-* -------------------------------------
+
+        # select nothing
+        assert pcss('li:nth-child(-n)') == []
+        # select all children
+        assert pcss('li:nth-child(n)') == [
+            'first-li', 'second-li', 'third-li', 'fourth-li',
+            'fifth-li', 'sixth-li', 'seventh-li']
+
+        assert pcss('li:nth-child(3)',
+                    '#first-li ~ :nth-child(3)') == ['third-li']
         assert pcss('li:nth-child(10)') == []
         assert pcss('li:nth-child(2n)', 'li:nth-child(even)',
                     'li:nth-child(2n+0)') == [
@@ -640,19 +691,36 @@ class TestCssselect(unittest.TestCase):
         assert pcss('li:nth-child(+2n+1)', 'li:nth-child(odd)') == [
             'first-li', 'third-li', 'fifth-li', 'seventh-li']
         assert pcss('li:nth-child(2n+4)') == ['fourth-li', 'sixth-li']
-        # FIXME: I'm not 100% sure this is right:
         assert pcss('li:nth-child(3n+1)') == [
             'first-li', 'fourth-li', 'seventh-li']
-        assert pcss('li:nth-last-child(0)') == [
-            'seventh-li']
+        assert pcss('li:nth-child(-n+3)') == [
+            'first-li', 'second-li', 'third-li']
+        assert pcss('li:nth-child(-2n+4)') == ['second-li', 'fourth-li']
+        assert pcss('li:nth-last-child(0)') == []
+        assert pcss('li:nth-last-child(1)') == ['seventh-li']
         assert pcss('li:nth-last-child(2n)', 'li:nth-last-child(even)') == [
             'second-li', 'fourth-li', 'sixth-li']
-        assert pcss('li:nth-last-child(2n+2)') == ['second-li', 'fourth-li']
+        assert pcss('li:nth-last-child(2n+1)') == [
+            'first-li', 'third-li', 'fifth-li', 'seventh-li']
+        assert pcss('li:nth-last-child(2n+2)') == [
+            'second-li', 'fourth-li', 'sixth-li']
+        assert pcss('li:nth-last-child(3n+1)') == [
+            'first-li', 'fourth-li', 'seventh-li']
         assert pcss('ol:first-of-type') == ['first-ol']
         assert pcss('ol:nth-child(1)') == []
         assert pcss('ol:nth-of-type(2)') == ['second-ol']
-        # FIXME: like above', '(1) or (2)?
-        assert pcss('ol:nth-last-of-type(1)') == ['first-ol']
+        assert pcss('ol:nth-last-of-type(1)') == ['second-ol']
+
+        # "+" and "~" tests
+        assert pcss('ol#first-ol li + li:nth-child(4)') == ['fourth-li']
+        assert pcss('li + li:nth-child(1)') == []
+        assert pcss('li ~ li:nth-child(2n+1)') == [
+            'third-li', 'fifth-li', 'seventh-li'
+        ]   # all but the first
+        assert pcss('li ~ li:nth-last-child(2n+1)') == [
+            'third-li', 'fifth-li', 'seventh-li'
+        ]   # all but the first
+
         assert pcss('span:only-child') == ['foobar-span']
         assert pcss('li div:only-child') == ['li-div']
         assert pcss('div *:only-child') == ['li-div', 'foobar-span']
@@ -693,6 +761,8 @@ class TestCssselect(unittest.TestCase):
         assert pcss('ol :Not(li[class])') == [
             'first-li', 'second-li', 'li-div',
             'fifth-li', 'sixth-li', 'seventh-li']
+        assert pcss('ol.a.b.c > li.c:nth-child(3)') == ['third-li']
+
         # Invalid characters in XPath element names, should not crash
         assert pcss(r'di\a0 v', r'div\[') == []
         assert pcss(r'[h\a0 ref]', r'[h\]ref]') == []
diff --git a/tox.ini b/tox.ini
index ca053d8..7a3359a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,8 +2,13 @@
 envlist = py25,py26,py27,py32,py33
 
 [testenv]
-deps=lxml
-commands = python cssselect/tests.py
+deps=
+    lxml
+    pytest<3
+    pytest-cov
+
+commands =
+    py.test --cov-report term --cov=cssselect
 
 [testenv:py25]
 setenv =

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-cssselect.git



More information about the Python-modules-commits mailing list