New Upstream Release - python-lesscpy

Ready changes

Summary

Merged new upstream version: 0.15.1+ds (was: 0.13.0+ds).

Resulting package

Built on 2022-11-13T19:23 (took 2m16s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-lesscpy

Lintian Result

Diff

diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..51f0419
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+James Page <james.page@ubuntu.com>
+Patrick <patrick@koumbit.org>
+Sascha Peilicke <sascha@peilicke.de>
+Simon de Haan <simon@praekeltfoundation.org>
+Jóhann T Maríusson <jtm@robot.is>
diff --git a/PKG-INFO b/PKG-INFO
index 39cd933..78c1d51 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,182 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: lesscpy
-Version: 0.13.0
+Version: 0.15.1
 Summary: Python LESS compiler
 Home-page: https://github.com/lesscpy/lesscpy
 Author: Jóhann T Maríusson
 Author-email: jtm@robot.is
 License: MIT
-Description-Content-Type: UNKNOWN
-Description: LESSCPY
-        =======
-        
-        .. image:: https://travis-ci.org/lesscpy/lesscpy.png?branch=master
-                :target: https://travis-ci.org/lesscpy/lesscpy
-        
-        .. image:: https://coveralls.io/repos/lesscpy/lesscpy/badge.png
-                :target: https://coveralls.io/r/lesscpy/lesscpy
-        
-        .. image:: https://pypip.in/d/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-        
-        .. image:: https://pypip.in/v/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-        
-        .. image:: https://pypip.in/wheel/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-                :alt: Wheel Status
-        
-        .. image:: https://pypip.in/license/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-                :alt: License
-        
-        Python LESS Compiler.
-        
-        A compiler written in Python for the LESS language. For those of us not willing
-        or able to have node.js installed in our environment. Not all features of LESS
-        are supported (yet). Some features wil probably never be supported (JavaScript
-        evaluation). This program uses PLY (Python Lex-Yacc) to tokenize / parse the
-        input and is considerably slower than the NodeJS compiler. The plan is to
-        utilize this to build in proper syntax checking and perhaps YUI compressing.
-        
-        This is an early version, so you are likely to find bugs.
-        
-        For more information on LESS:
-          http://lesscss.org/ or https://github.com/cloudhead/less.js
-         
-        Development files:
-          https://github.com/lesscpy/lesscpy
-        
-        
-        Supported features
-        ------------------
-        
-        - Variables
-        - String interpolation
-        - Mixins (nested, calls, closures, recursive)
-        - Guard expressions
-        - Parametered mixins (class / id)
-        - @arguments
-        - Nesting
-        - Escapes ~/e()
-        - Expressions
-        - Keyframe blocks
-        - Color functions (lighten, darken, saturate, desaturate, spin, hue, mix,
-                           saturation, lightness)
-        - Other functions (round, increment, decrement, format '%(', ...)
-        
-        
-        Differences from less.js
-        ------------------------
-        
-        - All colors are auto-formatted to #nnnnnn. eg, #f7e923
-        - Does not preserve CSS comments
-        
-        
-        Not supported
-        -------------
-        
-        - JavaScript evaluation
-         
-        
-        Requirements
-        ------------
-        
-        - Python 2.6, 2.7, 3.3, 3.4, or 3.5
-        - ply (Python Lex-Yacc) (check requirements.txt)
-         
-        
-        Installation
-        ------------
-        
-        To install lesscpy from the `Python Package Index`_, simply:
-        
-        .. code-block:: bash
-        
-            $ pip install lesscpy
-        
-        To do a local system-wide install:
-        
-        .. code-block:: bash
-        
-            python setup.py install
-         
-        Or simply place the package into your Python path. Or rather use packages
-        provided by your distribution (openSUSE has them at least).
-        
-        
-        Compiler script Usage
-        ---------------------
-         
-        .. code-block:: text
-        
-            usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-x] [-X] [-t] [-s SPACES] [-o OUT]
-                           [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N]
-                           target
-        
-            LessCss Compiler
-        
-            positional arguments:
-              target                less file or directory
-        
-            optional arguments:
-              -h, --help            show this help message and exit
-              -v, --version         show program's version number and exit
-              -I INCLUDE, --include INCLUDE
-                                    Included less-files (comma separated)
-              -V, --verbose         Verbose mode
-        
-            Formatting options:
-              -x, --minify          Minify output
-              -X, --xminify         Minify output, no end of block newlines
-              -t, --tabs            Use tabs
-              -s SPACES, --spaces SPACES
-                                    Number of startline spaces (default 2)
-        
-            Directory options:
-              Compiles all \*.less files in directory that have a newer timestamp than
-              it's css file.
-        
-              -o OUT, --out OUT     Output directory
-              -r, --recurse         Recursive into subdirectorys
-              -f, --force           Force recompile on all files
-              -m, --min-ending      Add '.min' into output filename. eg, name.min.css
-              -D, --dry-run         Dry run, do not write files
-        
-            Debugging:
-              -g, --debug           Debugging information
-              -S, --scopemap        Scopemap
-              -L, --lex-only        Run lexer on target
-              -N, --no-css          No css output
-        
-        
-        Python usage
-        ------------
-        
-        If you want to use the compiler from within Python, you can do it like this:
-        
-        .. code-block:: python
-        
-            import lesscpy
-            from six import StringIO
-        
-            print(lesscpy.compile(StringIO(u"a { border-width: 2px * 3; }"), minify=True))
-        
-        The output will be:
-        
-        .. code-block:: text
-        
-            a{border-width:6px;}
-        
-        License
-        -------
-        
-        See the LICENSE file
-        
-        
-        .. _`Python Package Index`: https://pypi.python.org/pypi/lesscpy
-        
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Console
 Classifier: Intended Audience :: End Users/Desktop
@@ -185,10 +14,188 @@ Classifier: Intended Audience :: System Administrators
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Code Generators
 Classifier: Topic :: Software Development :: Pre-processors
+License-File: LICENSE
+License-File: AUTHORS
+
+LESSCPY
+=======
+
+.. image:: https://travis-ci.org/lesscpy/lesscpy.png?branch=master
+        :target: https://travis-ci.org/lesscpy/lesscpy
+
+.. image:: https://coveralls.io/repos/lesscpy/lesscpy/badge.png
+        :target: https://coveralls.io/r/lesscpy/lesscpy
+
+.. image:: https://img.shields.io/pypi/dm/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+
+.. image:: https://img.shields.io/pypi/v/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+
+.. image:: https://img.shields.io/pypi/wheel/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+        :alt: Wheel Status
+
+.. image:: https://img.shields.io/pypi/l/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+        :alt: License
+
+Python LESS Compiler.
+
+A compiler written in Python for the LESS language. For those of us not willing
+or able to have node.js installed in our environment. Not all features of LESS
+are supported (yet). Some features wil probably never be supported (JavaScript
+evaluation). This program uses PLY (Python Lex-Yacc) to tokenize / parse the
+input and is considerably slower than the NodeJS compiler. The plan is to
+utilize this to build in proper syntax checking and perhaps YUI compressing.
+
+This is an early version, so you are likely to find bugs.
+
+For more information on LESS:
+  http://lesscss.org/ or https://github.com/cloudhead/less.js
+ 
+Development files:
+  https://github.com/lesscpy/lesscpy
+
+
+Supported features
+------------------
+
+- Variables
+- String interpolation
+- Mixins (nested, calls, closures, recursive)
+- Guard expressions
+- Parametered mixins (class / id)
+- @arguments
+- Nesting
+- Escapes ~/e()
+- Expressions
+- Keyframe blocks
+- Color functions (lighten, darken, saturate, desaturate, spin, hue, mix,
+                   saturation, lightness)
+- Other functions (round, increment, decrement, format '%(', ...)
+
+
+Differences from less.js
+------------------------
+
+- All colors are auto-formatted to #nnnnnn. eg, #f7e923
+- Does not preserve CSS comments
+
+
+Not supported
+-------------
+
+- JavaScript evaluation
+ 
+
+Requirements
+------------
+
+- Python 3.7 - 3.11,
+- ply (Python Lex-Yacc) (check requirements.txt)
+
+Installation
+------------
+
+To install lesscpy from the `Python Package Index`_, simply:
+
+.. code-block:: bash
+
+    $ pip install lesscpy
+
+To do a local system-wide install:
+
+.. code-block:: bash
+
+    python setup.py install
+ 
+Or simply place the package into your Python path. Or rather use packages
+provided by your distribution (openSUSE has them at least).
+
+
+Compiler script Usage
+---------------------
+ 
+.. code-block:: text
+
+    usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-C] [-x] [-X] [-t] [-s SPACES]
+                   [-o OUT] [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N]
+                   target [output]
+
+    LessCss Compiler
+
+    positional arguments:
+      target                less file or directory
+      output                output file path
+
+    optional arguments:
+      -h, --help            show this help message and exit
+      -v, --version         show program's version number and exit
+      -I INCLUDE, --include INCLUDE
+                            Included less-files (comma separated)
+      -V, --verbose         Verbose mode
+      -C, --dont_create_dirs
+                            Creates directories when outputing files (lessc non-
+                            compatible)
+
+    Formatting options:
+      -x, --minify          Minify output
+      -X, --xminify         Minify output, no end of block newlines
+      -t, --tabs            Use tabs
+      -s SPACES, --spaces SPACES
+                            Number of startline spaces (default 2)
+
+    Directory options:
+      Compiles all *.less files in directory that have a newer timestamp than
+      it's css file.
+
+      -o OUT, --out OUT     Output directory
+      -r, --recurse         Recursive into subdirectorys
+      -f, --force           Force recompile on all files
+      -m, --min-ending      Add '.min' into output filename. eg, name.min.css
+      -D, --dry-run         Dry run, do not write files
+
+    Debugging:
+      -g, --debug           Debugging information
+      -S, --scopemap        Scopemap
+      -L, --lex-only        Run lexer on target
+      -N, --no-css          No css output
+
+    << jtm@robot.is @_o >>
+
+Python usage
+------------
+
+If you want to use the compiler from within Python, you can do it like this:
+
+.. code-block:: python
+
+    import lesscpy
+
+    print(lesscpy.compile(StringIO(u"a { border-width: 2px * 3; }"), minify=True))
+
+The output will be:
+
+.. code-block:: text
+
+    a{border-width:6px;}
+
+License
+-------
+
+See the LICENSE file
+
+
+.. _`Python Package Index`: https://pypi.python.org/pypi/lesscpy
diff --git a/README.rst b/README.rst
index 06c4ff9..4e40bf3 100644
--- a/README.rst
+++ b/README.rst
@@ -7,17 +7,17 @@ LESSCPY
 .. image:: https://coveralls.io/repos/lesscpy/lesscpy/badge.png
         :target: https://coveralls.io/r/lesscpy/lesscpy
 
-.. image:: https://pypip.in/d/lesscpy/badge.png
+.. image:: https://img.shields.io/pypi/dm/lesscpy.svg
         :target: https://pypi.python.org/pypi/lesscpy
 
-.. image:: https://pypip.in/v/lesscpy/badge.png
+.. image:: https://img.shields.io/pypi/v/lesscpy.svg
         :target: https://pypi.python.org/pypi/lesscpy
 
-.. image:: https://pypip.in/wheel/lesscpy/badge.png
+.. image:: https://img.shields.io/pypi/wheel/lesscpy.svg
         :target: https://pypi.python.org/pypi/lesscpy
         :alt: Wheel Status
 
-.. image:: https://pypip.in/license/lesscpy/badge.png
+.. image:: https://img.shields.io/pypi/l/lesscpy.svg
         :target: https://pypi.python.org/pypi/lesscpy
         :alt: License
 
@@ -73,9 +73,8 @@ Not supported
 Requirements
 ------------
 
-- Python 2.6, 2.7, 3.3, 3.4, or 3.5
+- Python 3.7 - 3.11,
 - ply (Python Lex-Yacc) (check requirements.txt)
- 
 
 Installation
 ------------
@@ -101,14 +100,15 @@ Compiler script Usage
  
 .. code-block:: text
 
-    usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-x] [-X] [-t] [-s SPACES] [-o OUT]
-                   [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N]
-                   target
+    usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-C] [-x] [-X] [-t] [-s SPACES]
+                   [-o OUT] [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N]
+                   target [output]
 
     LessCss Compiler
 
     positional arguments:
       target                less file or directory
+      output                output file path
 
     optional arguments:
       -h, --help            show this help message and exit
@@ -116,6 +116,9 @@ Compiler script Usage
       -I INCLUDE, --include INCLUDE
                             Included less-files (comma separated)
       -V, --verbose         Verbose mode
+      -C, --dont_create_dirs
+                            Creates directories when outputing files (lessc non-
+                            compatible)
 
     Formatting options:
       -x, --minify          Minify output
@@ -125,7 +128,7 @@ Compiler script Usage
                             Number of startline spaces (default 2)
 
     Directory options:
-      Compiles all \*.less files in directory that have a newer timestamp than
+      Compiles all *.less files in directory that have a newer timestamp than
       it's css file.
 
       -o OUT, --out OUT     Output directory
@@ -140,6 +143,7 @@ Compiler script Usage
       -L, --lex-only        Run lexer on target
       -N, --no-css          No css output
 
+    << jtm@robot.is @_o >>
 
 Python usage
 ------------
@@ -149,7 +153,6 @@ If you want to use the compiler from within Python, you can do it like this:
 .. code-block:: python
 
     import lesscpy
-    from six import StringIO
 
     print(lesscpy.compile(StringIO(u"a { border-width: 2px * 3; }"), minify=True))
 
diff --git a/debian/changelog b/debian/changelog
index 57b32e3..7b7ac5b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-lesscpy (0.15.1+ds-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 13 Nov 2022 19:22:01 -0000
+
 python-lesscpy (0.13.0+ds-2) unstable; urgency=medium
 
   * Unnecessary postinst and prerm and add preinst for upgrade
diff --git a/lesscpy.egg-info/PKG-INFO b/lesscpy.egg-info/PKG-INFO
index 39cd933..78c1d51 100644
--- a/lesscpy.egg-info/PKG-INFO
+++ b/lesscpy.egg-info/PKG-INFO
@@ -1,182 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: lesscpy
-Version: 0.13.0
+Version: 0.15.1
 Summary: Python LESS compiler
 Home-page: https://github.com/lesscpy/lesscpy
 Author: Jóhann T Maríusson
 Author-email: jtm@robot.is
 License: MIT
-Description-Content-Type: UNKNOWN
-Description: LESSCPY
-        =======
-        
-        .. image:: https://travis-ci.org/lesscpy/lesscpy.png?branch=master
-                :target: https://travis-ci.org/lesscpy/lesscpy
-        
-        .. image:: https://coveralls.io/repos/lesscpy/lesscpy/badge.png
-                :target: https://coveralls.io/r/lesscpy/lesscpy
-        
-        .. image:: https://pypip.in/d/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-        
-        .. image:: https://pypip.in/v/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-        
-        .. image:: https://pypip.in/wheel/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-                :alt: Wheel Status
-        
-        .. image:: https://pypip.in/license/lesscpy/badge.png
-                :target: https://pypi.python.org/pypi/lesscpy
-                :alt: License
-        
-        Python LESS Compiler.
-        
-        A compiler written in Python for the LESS language. For those of us not willing
-        or able to have node.js installed in our environment. Not all features of LESS
-        are supported (yet). Some features wil probably never be supported (JavaScript
-        evaluation). This program uses PLY (Python Lex-Yacc) to tokenize / parse the
-        input and is considerably slower than the NodeJS compiler. The plan is to
-        utilize this to build in proper syntax checking and perhaps YUI compressing.
-        
-        This is an early version, so you are likely to find bugs.
-        
-        For more information on LESS:
-          http://lesscss.org/ or https://github.com/cloudhead/less.js
-         
-        Development files:
-          https://github.com/lesscpy/lesscpy
-        
-        
-        Supported features
-        ------------------
-        
-        - Variables
-        - String interpolation
-        - Mixins (nested, calls, closures, recursive)
-        - Guard expressions
-        - Parametered mixins (class / id)
-        - @arguments
-        - Nesting
-        - Escapes ~/e()
-        - Expressions
-        - Keyframe blocks
-        - Color functions (lighten, darken, saturate, desaturate, spin, hue, mix,
-                           saturation, lightness)
-        - Other functions (round, increment, decrement, format '%(', ...)
-        
-        
-        Differences from less.js
-        ------------------------
-        
-        - All colors are auto-formatted to #nnnnnn. eg, #f7e923
-        - Does not preserve CSS comments
-        
-        
-        Not supported
-        -------------
-        
-        - JavaScript evaluation
-         
-        
-        Requirements
-        ------------
-        
-        - Python 2.6, 2.7, 3.3, 3.4, or 3.5
-        - ply (Python Lex-Yacc) (check requirements.txt)
-         
-        
-        Installation
-        ------------
-        
-        To install lesscpy from the `Python Package Index`_, simply:
-        
-        .. code-block:: bash
-        
-            $ pip install lesscpy
-        
-        To do a local system-wide install:
-        
-        .. code-block:: bash
-        
-            python setup.py install
-         
-        Or simply place the package into your Python path. Or rather use packages
-        provided by your distribution (openSUSE has them at least).
-        
-        
-        Compiler script Usage
-        ---------------------
-         
-        .. code-block:: text
-        
-            usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-x] [-X] [-t] [-s SPACES] [-o OUT]
-                           [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N]
-                           target
-        
-            LessCss Compiler
-        
-            positional arguments:
-              target                less file or directory
-        
-            optional arguments:
-              -h, --help            show this help message and exit
-              -v, --version         show program's version number and exit
-              -I INCLUDE, --include INCLUDE
-                                    Included less-files (comma separated)
-              -V, --verbose         Verbose mode
-        
-            Formatting options:
-              -x, --minify          Minify output
-              -X, --xminify         Minify output, no end of block newlines
-              -t, --tabs            Use tabs
-              -s SPACES, --spaces SPACES
-                                    Number of startline spaces (default 2)
-        
-            Directory options:
-              Compiles all \*.less files in directory that have a newer timestamp than
-              it's css file.
-        
-              -o OUT, --out OUT     Output directory
-              -r, --recurse         Recursive into subdirectorys
-              -f, --force           Force recompile on all files
-              -m, --min-ending      Add '.min' into output filename. eg, name.min.css
-              -D, --dry-run         Dry run, do not write files
-        
-            Debugging:
-              -g, --debug           Debugging information
-              -S, --scopemap        Scopemap
-              -L, --lex-only        Run lexer on target
-              -N, --no-css          No css output
-        
-        
-        Python usage
-        ------------
-        
-        If you want to use the compiler from within Python, you can do it like this:
-        
-        .. code-block:: python
-        
-            import lesscpy
-            from six import StringIO
-        
-            print(lesscpy.compile(StringIO(u"a { border-width: 2px * 3; }"), minify=True))
-        
-        The output will be:
-        
-        .. code-block:: text
-        
-            a{border-width:6px;}
-        
-        License
-        -------
-        
-        See the LICENSE file
-        
-        
-        .. _`Python Package Index`: https://pypi.python.org/pypi/lesscpy
-        
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Console
 Classifier: Intended Audience :: End Users/Desktop
@@ -185,10 +14,188 @@ Classifier: Intended Audience :: System Administrators
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Code Generators
 Classifier: Topic :: Software Development :: Pre-processors
+License-File: LICENSE
+License-File: AUTHORS
+
+LESSCPY
+=======
+
+.. image:: https://travis-ci.org/lesscpy/lesscpy.png?branch=master
+        :target: https://travis-ci.org/lesscpy/lesscpy
+
+.. image:: https://coveralls.io/repos/lesscpy/lesscpy/badge.png
+        :target: https://coveralls.io/r/lesscpy/lesscpy
+
+.. image:: https://img.shields.io/pypi/dm/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+
+.. image:: https://img.shields.io/pypi/v/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+
+.. image:: https://img.shields.io/pypi/wheel/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+        :alt: Wheel Status
+
+.. image:: https://img.shields.io/pypi/l/lesscpy.svg
+        :target: https://pypi.python.org/pypi/lesscpy
+        :alt: License
+
+Python LESS Compiler.
+
+A compiler written in Python for the LESS language. For those of us not willing
+or able to have node.js installed in our environment. Not all features of LESS
+are supported (yet). Some features wil probably never be supported (JavaScript
+evaluation). This program uses PLY (Python Lex-Yacc) to tokenize / parse the
+input and is considerably slower than the NodeJS compiler. The plan is to
+utilize this to build in proper syntax checking and perhaps YUI compressing.
+
+This is an early version, so you are likely to find bugs.
+
+For more information on LESS:
+  http://lesscss.org/ or https://github.com/cloudhead/less.js
+ 
+Development files:
+  https://github.com/lesscpy/lesscpy
+
+
+Supported features
+------------------
+
+- Variables
+- String interpolation
+- Mixins (nested, calls, closures, recursive)
+- Guard expressions
+- Parametered mixins (class / id)
+- @arguments
+- Nesting
+- Escapes ~/e()
+- Expressions
+- Keyframe blocks
+- Color functions (lighten, darken, saturate, desaturate, spin, hue, mix,
+                   saturation, lightness)
+- Other functions (round, increment, decrement, format '%(', ...)
+
+
+Differences from less.js
+------------------------
+
+- All colors are auto-formatted to #nnnnnn. eg, #f7e923
+- Does not preserve CSS comments
+
+
+Not supported
+-------------
+
+- JavaScript evaluation
+ 
+
+Requirements
+------------
+
+- Python 3.7 - 3.11,
+- ply (Python Lex-Yacc) (check requirements.txt)
+
+Installation
+------------
+
+To install lesscpy from the `Python Package Index`_, simply:
+
+.. code-block:: bash
+
+    $ pip install lesscpy
+
+To do a local system-wide install:
+
+.. code-block:: bash
+
+    python setup.py install
+ 
+Or simply place the package into your Python path. Or rather use packages
+provided by your distribution (openSUSE has them at least).
+
+
+Compiler script Usage
+---------------------
+ 
+.. code-block:: text
+
+    usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-C] [-x] [-X] [-t] [-s SPACES]
+                   [-o OUT] [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N]
+                   target [output]
+
+    LessCss Compiler
+
+    positional arguments:
+      target                less file or directory
+      output                output file path
+
+    optional arguments:
+      -h, --help            show this help message and exit
+      -v, --version         show program's version number and exit
+      -I INCLUDE, --include INCLUDE
+                            Included less-files (comma separated)
+      -V, --verbose         Verbose mode
+      -C, --dont_create_dirs
+                            Creates directories when outputing files (lessc non-
+                            compatible)
+
+    Formatting options:
+      -x, --minify          Minify output
+      -X, --xminify         Minify output, no end of block newlines
+      -t, --tabs            Use tabs
+      -s SPACES, --spaces SPACES
+                            Number of startline spaces (default 2)
+
+    Directory options:
+      Compiles all *.less files in directory that have a newer timestamp than
+      it's css file.
+
+      -o OUT, --out OUT     Output directory
+      -r, --recurse         Recursive into subdirectorys
+      -f, --force           Force recompile on all files
+      -m, --min-ending      Add '.min' into output filename. eg, name.min.css
+      -D, --dry-run         Dry run, do not write files
+
+    Debugging:
+      -g, --debug           Debugging information
+      -S, --scopemap        Scopemap
+      -L, --lex-only        Run lexer on target
+      -N, --no-css          No css output
+
+    << jtm@robot.is @_o >>
+
+Python usage
+------------
+
+If you want to use the compiler from within Python, you can do it like this:
+
+.. code-block:: python
+
+    import lesscpy
+
+    print(lesscpy.compile(StringIO(u"a { border-width: 2px * 3; }"), minify=True))
+
+The output will be:
+
+.. code-block:: text
+
+    a{border-width:6px;}
+
+License
+-------
+
+See the LICENSE file
+
+
+.. _`Python Package Index`: https://pypi.python.org/pypi/lesscpy
diff --git a/lesscpy.egg-info/SOURCES.txt b/lesscpy.egg-info/SOURCES.txt
index 6aaa820..99a34b2 100644
--- a/lesscpy.egg-info/SOURCES.txt
+++ b/lesscpy.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
+AUTHORS
 LICENSE
 MANIFEST.in
 README.rst
@@ -53,7 +54,6 @@ test/test_color.py
 test/test_color.pyc
 test/test_expression.py
 test/test_expression.pyc
-test/test_font_awesome.pyc
 test/test_identifier.py
 test/test_identifier.pyc
 test/test_issues.py
@@ -68,6 +68,30 @@ test/test_pycompile.py
 test/test_pycompile.pyc
 test/test_utility.py
 test/test_utility.pyc
+test/__pycache__/__init__.cpython-37.pyc
+test/__pycache__/__init__.cpython-39.pyc
+test/__pycache__/core.cpython-37.pyc
+test/__pycache__/core.cpython-39.pyc
+test/__pycache__/test_bootstrap3.cpython-37.pyc
+test/__pycache__/test_bootstrap3.cpython-39.pyc
+test/__pycache__/test_color.cpython-37.pyc
+test/__pycache__/test_color.cpython-39.pyc
+test/__pycache__/test_expression.cpython-37.pyc
+test/__pycache__/test_expression.cpython-39.pyc
+test/__pycache__/test_identifier.cpython-37.pyc
+test/__pycache__/test_identifier.cpython-39.pyc
+test/__pycache__/test_issues.cpython-37.pyc
+test/__pycache__/test_issues.cpython-39.pyc
+test/__pycache__/test_less.cpython-37.pyc
+test/__pycache__/test_less.cpython-39.pyc
+test/__pycache__/test_lexer.cpython-37.pyc
+test/__pycache__/test_lexer.cpython-39.pyc
+test/__pycache__/test_parser.cpython-37.pyc
+test/__pycache__/test_parser.cpython-39.pyc
+test/__pycache__/test_pycompile.cpython-37.pyc
+test/__pycache__/test_pycompile.cpython-39.pyc
+test/__pycache__/test_utility.cpython-37.pyc
+test/__pycache__/test_utility.cpython-39.pyc
 test/bootstrap3/css/bootstrap.css
 test/bootstrap3/css/bootstrap.min.css
 test/bootstrap3/css/theme.css
@@ -120,6 +144,8 @@ test/css/colors.css
 test/css/colors.min.css
 test/css/comments.css
 test/css/comments.min.css
+test/css/css-variables.css
+test/css/css-variables.min.css
 test/css/elements.css
 test/css/elements.min.css
 test/css/escapes.css
@@ -138,6 +164,10 @@ test/css/imports.css
 test/css/imports.min.css
 test/css/keyframes.css
 test/css/keyframes.min.css
+test/css/media-nested-2.css
+test/css/media-nested-2.min.css
+test/css/media-nested.css
+test/css/media-nested.min.css
 test/css/media.css
 test/css/media.min.css
 test/css/mixin-args-guards.css
@@ -184,11 +214,14 @@ test/css/issues/issue31.css
 test/css/issues/issue4.css
 test/css/issues/issue5.css
 test/css/issues/issue6.css
+test/css/issues/issue61.css
+test/css/issues/issue65.css
 test/css/issues/issue67.css
 test/less/argb.less
 test/less/calls.less
 test/less/colors.less
 test/less/comments.less
+test/less/css-variables.less
 test/less/elements.less
 test/less/escapes.less
 test/less/expressions.less
@@ -198,6 +231,8 @@ test/less/identifiers.less
 test/less/ie.less
 test/less/imports.less
 test/less/keyframes.less
+test/less/media-nested-2.less
+test/less/media-nested.less
 test/less/media.less
 test/less/mixin-args-guards.less
 test/less/mixin-args-local-calls.less
@@ -228,4 +263,6 @@ test/less/issues/issue31.less
 test/less/issues/issue4.less
 test/less/issues/issue5.less
 test/less/issues/issue6.less
+test/less/issues/issue61.less
+test/less/issues/issue65.less
 test/less/issues/issue67.less
\ No newline at end of file
diff --git a/lesscpy.egg-info/entry_points.txt b/lesscpy.egg-info/entry_points.txt
index 3db0fb1..ccb4df2 100644
--- a/lesscpy.egg-info/entry_points.txt
+++ b/lesscpy.egg-info/entry_points.txt
@@ -1,3 +1,2 @@
 [console_scripts]
 lesscpy = lesscpy.scripts.compiler:run
-
diff --git a/lesscpy.egg-info/requires.txt b/lesscpy.egg-info/requires.txt
index 4efa1dc..90412f0 100644
--- a/lesscpy.egg-info/requires.txt
+++ b/lesscpy.egg-info/requires.txt
@@ -1,2 +1 @@
 ply
-six
diff --git a/lesscpy/__init__.py b/lesscpy/__init__.py
index 3ac4ae4..d22e489 100644
--- a/lesscpy/__init__.py
+++ b/lesscpy/__init__.py
@@ -1,4 +1,4 @@
-__version_info__ = ('0', '13', '0')
+__version_info__ = ('0', '15', '1')
 __version__ = '.'.join(__version_info__)
 
 
@@ -18,4 +18,3 @@ def compile(file, minify=False, xminify=False, tabs=False, spaces=True):
     p.parse(file=file)
     f = formatter.Formatter(opt)
     return f.format(p)
-
diff --git a/lesscpy/__main__.py b/lesscpy/__main__.py
index 92b5465..7c9e459 100644
--- a/lesscpy/__main__.py
+++ b/lesscpy/__main__.py
@@ -2,4 +2,3 @@ from lesscpy.scripts.compiler import run
 
 if __name__ == '__main__':
     run()
-
diff --git a/lesscpy/lessc/color.py b/lesscpy/lessc/color.py
index 84b679c..d734233 100644
--- a/lesscpy/lessc/color.py
+++ b/lesscpy/lessc/color.py
@@ -12,13 +12,12 @@ import operator
 
 import colorsys
 import re
-import six
+from six import string_types
 from . import utility
 from lesscpy.lib import colors
 
 
-class Color():
-
+class Color:
     def process(self, expression):
         """ Process color expression
         args:
@@ -70,10 +69,9 @@ class Color():
                 return self._rgbatohex(list(map(int, args)))
             except ValueError:
                 if all((a for a in args
-                        if a[-1] == '%'
-                        and 100 >= int(a[:-1]) >= 0)):
-                    return self._rgbatohex([int(a[:-1]) * 255 / 100.0
-                                            for a in args])
+                        if a[-1] == '%' and 100 >= int(a[:-1]) >= 0)):
+                    return self._rgbatohex(
+                        [int(a[:-1]) * 255 / 100.0 for a in args])
         raise ValueError('Illegal color values')
 
     def rgba(self, *args):
@@ -94,15 +92,14 @@ class Color():
                 return self._rgbatohex(list(map(int, args)))
             except ValueError:
                 if all((a for a in args
-                        if a[-1] == '%'
-                        and 100 >= int(a[:-1]) >= 0)):
+                        if a[-1] == '%' and 100 >= int(a[:-1]) >= 0)):
                     alpha = list(args)[3]
                     if alpha[-1] == '%' and float(alpha[:-1]) == 0:
-                        values = self._rgbatohex_raw([int(a[:-1]) * 255 / 100.0
-                                                     for a in args])
+                        values = self._rgbatohex_raw(
+                            [int(a[:-1]) * 255 / 100.0 for a in args])
                         return "rgba(%s)" % ','.join([str(a) for a in values])
-                    return self._rgbatohex([int(a[:-1]) * 255 / 100.0
-                                            for a in args])
+                    return self._rgbatohex(
+                        [int(a[:-1]) * 255 / 100.0 for a in args])
         raise ValueError('Illegal color values')
 
     def argb(self, *args):
@@ -135,16 +132,17 @@ class Color():
                 if fval > 1:
                     rgb = [255] + rgb[1:]  # Clip invalid integer/float values
                 elif 1 >= fval >= 0:
-                    rgb = [fval * 256] + rgb[1:]  # Convert 0-1 to 0-255 range for _rgbatohex
+                    rgb = [
+                              fval * 256
+                          ] + rgb[1:]  # Convert 0-1 to 0-255 range for _rgbatohex
                 else:
                     rgb = [0] + rgb[1:]  # Clip lower bound
                 return self._rgbatohex(list(map(int, rgb)))
             except ValueError:
                 if all((a for a in rgb
-                        if a[-1] == '%'
-                        and 100 >= int(a[:-1]) >= 0)):
-                    return self._rgbatohex([int(a[:-1]) * 255 / 100.0
-                                            for a in rgb])
+                        if a[-1] == '%' and 100 >= int(a[:-1]) >= 0)):
+                    return self._rgbatohex(
+                        [int(a[:-1]) * 255 / 100.0 for a in rgb])
         raise ValueError('Illegal color values')
 
     def hsl(self, *args):
@@ -158,7 +156,8 @@ class Color():
             return self.hsla(*args)
         elif len(args) == 3:
             h, s, l = args
-            rgb = colorsys.hls_to_rgb(int(h) / 360.0, utility.pc_or_float(l), utility.pc_or_float(s))
+            rgb = colorsys.hls_to_rgb(
+                int(h) / 360.0, utility.pc_or_float(l), utility.pc_or_float(s))
             color = (utility.convergent_round(c * 255) for c in rgb)
             return self._rgbatohex(color)
         raise ValueError('Illegal color values')
@@ -172,7 +171,8 @@ class Color():
         """
         if len(args) == 4:
             h, s, l, a = args
-            rgb = colorsys.hls_to_rgb(int(h) / 360.0, utility.pc_or_float(l), utility.pc_or_float(s))
+            rgb = colorsys.hls_to_rgb(
+                int(h) / 360.0, utility.pc_or_float(l), utility.pc_or_float(s))
             color = [float(utility.convergent_round(c * 255)) for c in rgb]
             color.append(utility.pc_or_float(a))
             return "rgba(%s,%s,%s,%s)" % tuple(color)
@@ -304,7 +304,7 @@ class Color():
             str
         """
         if color and degree:
-            if isinstance(degree, six.string_types):
+            if isinstance(degree, string_types):
                 degree = float(degree.strip('%'))
             h, l, s = self._hextohls(color)
             h = ((h * 360.0) + degree) % 360.0
@@ -348,14 +348,14 @@ class Color():
             str
         """
         if color1 and color2:
-            if isinstance(weight, six.string_types):
+            if isinstance(weight, string_types):
                 weight = float(weight.strip('%'))
             weight = ((weight / 100.0) * 2) - 1
             rgb1 = self._hextorgb(color1)
             rgb2 = self._hextorgb(color2)
             alpha = 0
-            w1 = (((weight if weight * alpha == -1
-                    else weight + alpha) / (1 + weight * alpha)) + 1)
+            w1 = (((weight if weight * alpha == -1 else weight + alpha) /
+                   (1 + weight * alpha)) + 1)
             w1 = w1 / 2.0
             w2 = 1 - w1
             rgb = [
@@ -384,17 +384,17 @@ class Color():
         raise ValueError('Cannot format non-color')
 
     def _rgbatohex_raw(self, rgba):
-        values = ["%x" % int(v) for v in
-                  [0xff if h > 0xff else 0 if h < 0 else h for h in rgba]]
+        values = [
+            "%x" % int(v)
+            for v in [0xff if h > 0xff else 0 if h < 0 else h for h in rgba]
+        ]
         return values
 
     def _rgbatohex(self, rgba):
-        return '#%s' % ''.join(["%02x" % int(v) for v in
-                                [0xff
-                                 if h > 0xff else
-                                 0 if h < 0 else h
-                                 for h in rgba]
-                                ])
+        return '#%s' % ''.join([
+            "%02x" % int(v)
+            for v in [0xff if h > 0xff else 0 if h < 0 else h for h in rgba]
+        ])
 
     def _hextorgb(self, hex):
         if hex.lower() in colors.lessColors:
@@ -417,7 +417,7 @@ class Color():
         return colorsys.rgb_to_hls(*[c / 255.0 for c in rgb])
 
     def _ophsl(self, color, diff, idx, operation):
-        if isinstance(diff, six.string_types):
+        if isinstance(diff, string_types):
             diff = float(diff.strip('%'))
         hls = list(self._hextohls(color))
         hls[idx] = self._clamp(operation(hls[idx], diff / 100.0))
diff --git a/lesscpy/lessc/formatter.py b/lesscpy/lessc/formatter.py
index 796b79e..b8dd797 100644
--- a/lesscpy/lessc/formatter.py
+++ b/lesscpy/lessc/formatter.py
@@ -10,7 +10,6 @@
 
 
 class Formatter(object):
-
     def __init__(self, args):
         self.args = args
 
@@ -25,21 +24,9 @@ class Formatter(object):
             self.args.minify = True
         self.items = {}
         if self.args.minify:
-            self.items.update({
-                'nl': '',
-                'tab': '',
-                'ws': '',
-                'eb': eb
-            })
+            self.items.update({'nl': '', 'tab': '', 'ws': '', 'eb': eb})
         else:
             tab = '\t' if self.args.tabs else ' ' * int(self.args.spaces)
-            self.items.update({
-                'nl': '\n',
-                'tab': tab,
-                'ws': ' ',
-                'eb': eb
-            })
-        self.out = [u.fmt(self.items)
-                    for u in parse.result
-                    if u]
+            self.items.update({'nl': '\n', 'tab': tab, 'ws': ' ', 'eb': eb})
+        self.out = [u.fmt(self.items) for u in parse.result if u]
         return ''.join(self.out).strip()
diff --git a/lesscpy/lessc/lexer.py b/lesscpy/lessc/lexer.py
index c29f13e..38a964f 100644
--- a/lesscpy/lessc/lexer.py
+++ b/lesscpy/lessc/lexer.py
@@ -31,73 +31,21 @@ class LessLexer:
     )
     literals = '<>=%!/*-+&'
     tokens = [
-        'css_ident',
-        'css_dom',
-        'css_class',
-        'css_id',
-        'css_property',
-        'css_vendor_property',
-        'css_comment',
-        'css_string',
-        'css_color',
-        'css_filter',
-        'css_number',
-        'css_important',
-        'css_vendor_hack',
-        'css_uri',
-        'css_ms_filter',
-        'css_keyframe_selector',
-
-        'css_media_type',
-        'css_media_feature',
-
-        't_and',
-        't_not',
-        't_only',
-
-        'less_variable',
-        'less_comment',
-        'less_open_format',
-        'less_when',
-        'less_and',
-        'less_not',
-
-        't_ws',
-        't_popen',
-        't_pclose',
-        't_semicolon',
-        't_tilde',
-        't_colon',
-        't_comma',
-
-        't_eopen',
-        't_eclose',
-
-        't_isopen',
-        't_isclose',
-
-        't_bopen',
-        't_bclose'
+        'css_ident', 'css_dom', 'css_class', 'css_id', 'css_property',
+        'css_vendor_property', 'css_user_property', 'css_comment',
+        'css_string', 'css_color', 'css_filter', 'css_number', 'css_important',
+        'css_vendor_hack', 'css_uri', 'css_ms_filter', 'css_keyframe_selector',
+        'css_media_type', 'css_media_feature', 't_and', 't_not', 't_only',
+        'less_variable', 'less_comment', 'less_open_format', 'less_when',
+        'less_and', 'less_not', 't_ws', 't_popen', 't_pclose', 't_semicolon',
+        't_tilde', 't_colon', 't_comma', 't_eopen', 't_eclose', 't_isopen',
+        't_isclose', 't_bopen', 't_bclose'
     ]
     tokens += list(set(reserved.tokens.values()))
     # Tokens with significant following whitespace
-    significant_ws = set([
-        'css_class',
-        'css_id',
-        'css_dom',
-        'css_property',
-        'css_vendor_property',
-        'css_ident',
-        'css_number',
-        'css_color',
-        'css_media_type',
-        'css_filter',
-        'less_variable',
-        't_and',
-        't_not',
-        't_only',
-        '&',
-    ])
+    significant_ws = {'css_class', 'css_id', 'css_dom', 'css_property', 'css_vendor_property', 'css_user_property', 'css_ident',
+                      'css_number', 'css_color', 'css_media_type', 'css_filter', 'less_variable', 't_and', 't_not',
+                      't_only', '&'}
     significant_ws.update(reserved.tokens.values())
 
     def __init__(self):
@@ -139,7 +87,7 @@ class LessLexer:
         return t
 
     def t_css_ident(self, t):
-        (r'([\-\.\#]?'
+        (r'((\-|\.|\#|\-\-)?'
          '([_a-z]'
          '|[\200-\377]'
          '|\\\[0-9a-f]{1,6}'
@@ -176,13 +124,17 @@ class LessLexer:
             t.type = 'less_not'
         elif v in ('from', 'to'):
             t.type = 'css_keyframe_selector'
-        elif v in css.propertys:
+        elif v in css.properties:
             t.type = 'css_property'
             t.lexer.in_property_decl = True
-        elif (v in dom.elements or v.lower() in dom.elements) and not t.lexer.in_property_decl:
+        elif (v in dom.elements
+              or v.lower() in dom.elements) and not t.lexer.in_property_decl:
             # DOM elements can't be part of property declarations, avoids ambiguity between 'rect' DOM
             # element and rect() CSS function.
             t.type = 'css_dom'
+        elif v.startswith("--"):
+            t.type = 'css_user_property'
+            t.lexer.in_property_decl = True
         elif c == '-':
             t.type = 'css_vendor_property'
             t.lexer.in_property_decl = True
@@ -447,8 +399,8 @@ class LessLexer:
 
     # Error handling rule
     def t_error(self, t):
-        raise SyntaxError("Illegal character '%s' line %d" %
-                          (t.value[0], t.lexer.lineno))
+        raise SyntaxError(
+            "Illegal character '%s' line %d" % (t.value[0], t.lexer.lineno))
         t.lexer.skip(1)
 
     # Build the lexer
@@ -493,8 +445,8 @@ class LessLexer:
             if not t:
                 return t
             if t.type == 't_ws' and (
-                self.pretok or (self.last
-                                and self.last.type not in self.significant_ws)):
+                    self.pretok or
+                (self.last and self.last.type not in self.significant_ws)):
                 continue
             self.pretok = False
             if t.type == 't_bclose' and self.last and self.last.type not in ['t_bopen', 't_bclose'] and self.last.type != 't_semicolon' \
diff --git a/lesscpy/lessc/parser.py b/lesscpy/lessc/parser.py
index f7d2650..a16a7d8 100644
--- a/lesscpy/lessc/parser.py
+++ b/lesscpy/lessc/parser.py
@@ -18,7 +18,7 @@ import os
 import tempfile
 import sys
 import ply.yacc
-import six
+from six import string_types
 
 from . import lexer
 from . import utility
@@ -27,12 +27,14 @@ from .color import Color
 from lesscpy.exceptions import CompilationError
 from lesscpy.plib import Block, Call, Deferred, Expression, Identifier, Mixin, NegatedExpression, Property, Statement, Variable, Import, KeyframeSelector
 
+
 class ErrorRegister(object):
     """
 
     Raises CompilationError when an error occurs.
 
     """
+
     def __init__(self):
         self.errors = []
 
@@ -45,12 +47,14 @@ class ErrorRegister(object):
 
     close = __close__
 
+
 class PrintErrorRegister(object):
     """
 
     Colored error output to stderr.
 
     """
+
     def __init__(self):
         self.has_errored = False
 
@@ -80,8 +84,7 @@ class LessParser(object):
                  outputdir=tempfile.gettempdir(),
                  importlvl=0,
                  verbose=False,
-                 fail_with_exc=False
-                 ):
+                 fail_with_exc=False):
         """ Parser object
 
             Kwargs:
@@ -102,19 +105,16 @@ class LessParser(object):
         if not tabfile:
             tabfile = 'yacctab'
 
-        self.ignored = ('css_comment', 'less_comment',
-                        'css_vendor_hack')
+        self.ignored = ('css_comment', 'less_comment', 'css_vendor_hack')
 
-        self.tokens = [t for t in self.lex.tokens
-                       if t not in self.ignored]
+        self.tokens = [t for t in self.lex.tokens if t not in self.ignored]
         self.parser = ply.yacc.yacc(
             module=self,
             start='tunit',
             debug=yacc_debug,
             optimize=yacc_optimize,
             tabmodule=tabfile,
-            outputdir=outputdir
-        )
+            outputdir=outputdir)
         self.scope = scope if scope else Scope()
         self.stash = {}
         self.result = None
@@ -125,7 +125,6 @@ class LessParser(object):
         else:
             self.register = PrintErrorRegister()
 
-
     def parse(self, filename=None, file=None, debuglevel=0):
         """ Parse file.
         kwargs:
@@ -150,8 +149,7 @@ class LessParser(object):
         self.target = filename
         if self.verbose and not self.fail_with_exc:
             print('Compiling target: %s' % filename, file=sys.stderr)
-        self.result = self.parser.parse(
-            file, lexer=self.lex, debug=debuglevel)
+        self.result = self.parser.parse(file, lexer=self.lex, debug=debuglevel)
 
         self.post_parse()
         self.register.close()
@@ -236,7 +234,7 @@ class LessParser(object):
         if self.importlvl > 8:
             raise ImportError(
                 'Recrusive import level too deep > 8 (circular import ?)')
-        if isinstance(p[3], six.string_types):
+        if isinstance(p[3], string_types):
             ipath = utility.destring(p[3])
         elif isinstance(p[3], list):
             p[3] = Import(p[3], p.lineno(4)).parse(self.scope)
@@ -244,7 +242,8 @@ class LessParser(object):
         elif isinstance(p[3], Call):
             # NOTE(saschpe): Always in the form of 'url("...");', so parse it
             # and retrieve the inner css_string. This whole func is messy.
-            p[3] = p[3].parse(self.scope)  # Store it as string, Statement.fmt expects it.
+            p[3] = p[3].parse(
+                self.scope)  # Store it as string, Statement.fmt expects it.
             ipath = utility.destring(p[3][4:-1])
         fn, fe = os.path.splitext(ipath)
         if not fe or fe.lower() == '.less':
@@ -254,8 +253,10 @@ class LessParser(object):
                     ipath += '.less'
                 filename = "%s%s%s" % (cpath, os.sep, ipath)
                 if os.path.exists(filename):
-                    recurse = LessParser(importlvl=self.importlvl + 1,
-                                         verbose=self.verbose, scope=self.scope)
+                    recurse = LessParser(
+                        importlvl=self.importlvl + 1,
+                        verbose=self.verbose,
+                        scope=self.scope)
                     recurse.parse(filename=filename, debuglevel=0)
                     p[0] = recurse.result
                 else:
@@ -493,6 +494,7 @@ class LessParser(object):
     def p_prop_open(self, p):
         """ prop_open               : property t_colon
                                     | vendor_property t_colon
+                                    | user_property t_colon
                                     | word t_colon
         """
         p[0] = (p[1][0], '')
@@ -770,6 +772,7 @@ class LessParser(object):
                             | word
                             | id
                             | css_uri
+                            | css_user_property
                             | '='
                             | fcall
         """
@@ -871,7 +874,7 @@ class LessParser(object):
         """ variable                : less_variable
                                     | less_variable t_ws
         """
-#        p[0] = p[1]
+        #        p[0] = p[1]
         p[0] = tuple(list(p)[1:])
 
     def p_color(self, p):
@@ -883,8 +886,8 @@ class LessParser(object):
             if len(p) > 2:
                 p[0] = [p[0], p[2]]
         except ValueError:
-            self.handle_error(
-                'Illegal color value `%s`' % p[1], p.lineno(1), 'W')
+            self.handle_error('Illegal color value `%s`' % p[1], p.lineno(1),
+                              'W')
             p[0] = p[1]
 
     def p_number(self, p):
@@ -966,6 +969,12 @@ class LessParser(object):
         """
         p[0] = tuple(list(p)[1:])
 
+    def p_user_property(self, p):
+        """ user_property             : css_user_property
+                                      | css_user_property t_ws
+        """
+        p[0] = tuple(list(p)[1:])
+
     def p_media_type(self, p):
         """ media_type                : css_media_type
                                       | css_media_type t_ws
@@ -1023,6 +1032,7 @@ class LessParser(object):
         'empty                        :'
         pass
 
+
 #
 #    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 #
diff --git a/lesscpy/lessc/scope.py b/lesscpy/lessc/scope.py
index 6f973d3..05d8ec2 100644
--- a/lesscpy/lessc/scope.py
+++ b/lesscpy/lessc/scope.py
@@ -7,13 +7,12 @@
     See LICENSE for details.
 .. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
 """
-import six
+from six import string_types
 
 from . import utility
 
 
 class Scope(list):
-
     """ Scope class. A stack implementation.
     """
 
@@ -53,9 +52,7 @@ class Scope(list):
         Returns:
             list
         """
-        return [r['__current__']
-                for r in self
-                if r['__current__']]
+        return [r['__current__'] for r in self if r['__current__']]
 
     def add_block(self, block):
         """Add block element to scope
@@ -125,9 +122,7 @@ class Scope(list):
     def _smixins(self, name):
         """Inner wrapper to search for mixins by name.
         """
-        return (self._mixins[name]
-                if name in self._mixins
-                else False)
+        return self._mixins[name] if name in self._mixins else False
 
     def blocks(self, name):
         """
@@ -195,7 +190,7 @@ class Scope(list):
             var = self.variables('@' + name[2:-1])
             if var is False:
                 raise SyntaxError('Unknown escaped variable %s' % name)
-            if isinstance(var.value[0], six.string_types):
+            if isinstance(var.value[0], string_types):
                 var.value[0] = utility.destring(var.value[0])
         else:
             var = self.variables(name)
diff --git a/lesscpy/lessc/utility.py b/lesscpy/lessc/utility.py
index 2f61e9c..9c23d29 100644
--- a/lesscpy/lessc/utility.py
+++ b/lesscpy/lessc/utility.py
@@ -10,13 +10,18 @@
 
 from __future__ import print_function
 
-import collections
 import itertools
 import math
 import re
 import sys
 from six import string_types
 
+try:
+    from collections.abc import Iterable
+except ImportError:
+    from collections import Iterable
+
+
 def flatten(lst):
     """Flatten list.
     Args:
@@ -25,7 +30,8 @@ def flatten(lst):
         generator
     """
     for elm in lst:
-        if isinstance(elm, collections.Iterable) and not isinstance(elm, string_types):
+        if isinstance(elm, Iterable) and not isinstance(
+                elm, string_types):
             for sub in flatten(elm):
                 yield sub
         else:
@@ -74,8 +80,8 @@ def blocksearch(block, name):
     """
     if hasattr(block, 'tokens'):
         for b in block.tokens[1]:
-            b = (b if hasattr(b, 'raw') and b.raw() == name
-                 else blocksearch(b, name))
+            b = (b if hasattr(b, 'raw') and b.raw() == name else blocksearch(
+                b, name))
             if b:
                 return b
     return False
@@ -89,12 +95,7 @@ def reverse_guard(lst):
     returns:
         list
     """
-    rev = {
-        '<': '>=',
-        '>': '=<',
-        '>=': '<',
-        '=<': '>'
-    }
+    rev = {'<': '>=', '>': '=<', '>=': '<', '=<': '>'}
     return [rev[l] if l in rev else l for l in lst]
 
 
@@ -138,16 +139,16 @@ def analyze_number(var, err=''):
     """
     n, u = split_unit(var)
     if not isinstance(var, string_types):
-        return (var, u)
+        return var, u
     if is_color(var):
-        return (var, 'color')
+        return var, 'color'
     if is_int(n):
         n = int(n)
     elif is_float(n):
         n = float(n)
     else:
         raise SyntaxError('%s ´%s´' % (err, var))
-    return (n, u)
+    return n, u
 
 
 def with_unit(number, unit=None):
@@ -196,10 +197,10 @@ def is_variable(value):
         bool
     """
     if isinstance(value, string_types):
-        return (value.startswith('@') or value.startswith('-@'))
+        return value.startswith('@') or value.startswith('-@')
     elif isinstance(value, tuple):
         value = ''.join(value)
-        return (value.startswith('@') or value.startswith('-@'))
+        return value.startswith('@') or value.startswith('-@')
     return False
 
 
@@ -278,6 +279,7 @@ def convergent_round(value, ndigits=0):
                 return math.ceil(nearest_even)
     return round(value, ndigits)
 
+
 def pc_or_float(s):
     """ Utility function to process strings that contain either percentiles or floats
     args:
@@ -289,6 +291,7 @@ def pc_or_float(s):
         return float(s.strip('%')) / 100.0
     return float(s)
 
+
 def permutations_with_replacement(iterable, r=None):
     """Return successive r length permutations of elements in the iterable.
 
diff --git a/lesscpy/lib/css.py b/lesscpy/lib/css.py
index 4404687..b411764 100644
--- a/lesscpy/lib/css.py
+++ b/lesscpy/lib/css.py
@@ -334,7 +334,7 @@ vendor_ugly = [
     'behavior',
     'zoom',
 ]
-propertys = css2 + css3 + svg + vendor_ugly
+properties = css2 + css3 + svg + vendor_ugly
 
 # CSS-2(.1) media types: http://www.w3.org/TR/CSS2/media.html#media-types
 # Include media types as defined in HTML4: http://www.w3.org/TR/1999/REC-html401-19991224/types.html#h-6.13
@@ -374,7 +374,6 @@ css3_media_features = [
     'min-aspect-ratio',
     'max-aspect-ratio',
     'device-aspect-ratio',
-
     'min-device-aspect-ratio',
     'max-device-aspect-ratio',
     'color',
diff --git a/lesscpy/lib/dom.py b/lesscpy/lib/dom.py
index 7057992..d20cdff 100644
--- a/lesscpy/lib/dom.py
+++ b/lesscpy/lib/dom.py
@@ -135,7 +135,6 @@ html5 = [
     'track',
     'video',
     'wbr',
-
     'only',  # TODO/FIXME: What is this?!?
 ]
 
diff --git a/lesscpy/lib/reserved.py b/lesscpy/lib/reserved.py
index 1c0df47..fbf8957 100644
--- a/lesscpy/lib/reserved.py
+++ b/lesscpy/lib/reserved.py
@@ -18,9 +18,7 @@ tokens = {
     '@-webkit-keyframes': 'css_keyframes',
     '@-ms-keyframes': 'css_keyframes',
     '@-o-keyframes': 'css_keyframes',
-
     '@viewport': 'css_viewport',
     '@-ms-viewport': 'css_viewport',
-
     '@arguments': 'less_arguments',
 }
diff --git a/lesscpy/plib/block.py b/lesscpy/plib/block.py
index d6a0bcb..d92b534 100644
--- a/lesscpy/plib/block.py
+++ b/lesscpy/plib/block.py
@@ -13,7 +13,6 @@ from lesscpy.plib.identifier import Identifier
 
 
 class Block(Node):
-
     """ Block node. Represents one parse-block.
     Can contain property nodes or other block nodes.
     identifier {
@@ -29,7 +28,7 @@ class Block(Node):
         raises:
             SyntaxError
         returns:
-            self
+            self, or a list of new blocks if media queries need to be rotated
         """
         if not self.parsed:
             scope.push()
@@ -43,89 +42,81 @@ class Block(Node):
             inner = list(utility.flatten([p.parse(scope) for p in inner if p]))
             self.parsed = []
             self.inner = []
-            if not hasattr(self, "inner_media_queries"):
-                self.inner_media_queries = []
+            # Because media queries need to be at the root in CSS, we rotate them
+            # up the parse tree. More specifically, in a situation like this where
+            # the current node is .foo:
+            #
+            #   .foo {
+            #       @media print {
+            #           /* ... */
+            #       }
+            #       .bar {
+            #           /* ... */
+            #       }
+            #   }
+            #
+            # The media query is rotated out after splitting the node, resulting in
+            # this tree:
+            #
+            #   @media print {
+            #       .foo {
+            #           /* ... */
+            #       }
+            #   }
+            #   .foo {
+            #       .bar {
+            #           /* ... */
+            #       }
+            #   }
+            #
+            # New media queries are returned as siblings of self. If .foo is a
+            # media query itself, conditions are merged. This process is recursive
+            # so that media queries bubble up and split nodes along the way.
+            inner_media_queries = []
+            sibling_media_queries = []
             for p in inner:
-                if p is not None:
-                    if isinstance(p, Block):
-                        if (len(scope) == 2 and p.tokens[1] is not None):
-                            p_is_mediaquery = p.name.tokens[0] == '@media'
-                            # Inner block @media ... { ... } is a nested media
-                            # query. But double-nested media queries have to be
-                            # removed and marked as well. While parsing ".foo",
-                            # both nested "@media print" and double-nested
-                            # "@media all" will be handled as we have to
-                            # re-arrange the scope and block layout quite a bit:
-                            #
-                            #   .foo {
-                            #       @media print {
-                            #           color: blue;
-                            #           @media screen { font-size: 12em; }
-                            #       }
-                            #   }
-                            #
-                            # Expected result:
-                            #
-                            #   @media print {
-                            #       .foo { color: blue; }
-                            #   }
-                            #   @media print and screen {
-                            #       .foo { font-size: 12 em; }
-                            #   }
-                            append_list = []
-                            reparse_p = False
-                            for child in p.tokens[1]:
-                                if isinstance(child, Block) and child.name.raw().startswith("@media"):
-                                    # Remove child from the nested media query, it will be re-added to
-                                    # the parent with 'merged' media query (see above example).
-                                    p.tokens[1].remove(child)
-                                    if p_is_mediaquery:  # Media query inside a & block
-                                        # Double-nested media query found. We remove it from 'p' and add
-                                        # it to this block with a new 'name'.
-                                        reparse_p = True
-                                        part_a = p.name.tokens[2:][0][0][0]
-                                        part_b = child.name.tokens[2:][0][0]
-                                        new_ident_tokens = ['@media', ' ', [part_a, (' ', 'and', ' '), part_b]]
-                                        # Parse child again with new @media $BLA {} part
-                                        child.tokens[0] = Identifier(new_ident_tokens)
-                                        child.parsed = None
-                                        child = child.parse(scope)
-                                    else:
-                                        child.block_name = p.name
-                                    append_list.append(child)
-                                if reparse_p:
-                                    p.parsed = None
-                                    p = p.parse(scope)
-                            if not p_is_mediaquery and not append_list:
-                                self.inner.append(p)
-                            else:
-                                append_list.insert(0, p)  # This media query should occur before it's children
-                                for media_query in append_list:
-                                    self.inner_media_queries.append(media_query)
-                            # NOTE(saschpe): The code is not recursive but we hope that people
-                            # wont use triple-nested media queries.
-                        else:
-                            self.inner.append(p)
-                    else:
-                        self.parsed.append(p)
-            if self.inner_media_queries:
-                # Nested media queries, we have to remove self from scope and
-                # push all nested @media ... {} blocks.
-                scope.remove_block(self, index=-2)
-                for mb in self.inner_media_queries:
-                    # New inner block with current name and media block contents
-                    if hasattr(mb, 'block_name'):
-                        cb_name = mb.block_name
-                    else:
-                        cb_name = self.tokens[0]
-                    cb = Block([cb_name, mb.tokens[1]]).parse(scope)
-                    # Replace inner block contents with new block
-                    new_mb = Block([mb.tokens[0], [cb]]).parse(scope)
-                    self.inner.append(new_mb)
-                    scope.add_block(new_mb)
+                if not isinstance(p, Block):
+                    self.parsed.append(p)
+                # TODO: By separating in two lists (inner and inner_media_queries),
+                # we change the final order of declarations.
+                elif p.tokens[1] is not None and p.name.tokens[0] == '@media':
+                    inner_media_queries.append(p)
+                else:
+                    self.inner.append(p)
+            for mb in inner_media_queries:
+                # If the current node is also a media query, create a merged media
+                # query for each inner media query.
+                if self.name.tokens[0] == '@media':
+                    part_a = self.name.tokens[2:][0][0][0]
+                    part_b = mb.name.tokens[2:][0]
+                    cond = [
+                        '@media', ' ', [
+                            part_a, (' ', 'and', ' '),
+                            part_b
+                        ]
+                    ]
+                    # TODO: mb.parsed + mb.inner reorders things again
+                    mb = Block([Identifier(cond), mb.parsed + mb.inner]).parse(scope)
+                    sibling_media_queries += mb
+                    for block in mb:
+                        scope.add_block(block)
+                # Otherwise, rotate inner media queries out of self.
+                else:
+                    cbs = Block([self.tokens[0], mb.parsed + mb.inner]).parse(scope)
+                    for cb in cbs:
+                        # Replace inner block contents with new block
+                        new_mb = Block([mb.tokens[0], [cb]]).parse(scope)
+                        sibling_media_queries += new_mb
+                        for block in new_mb:
+                            scope.add_block(block)
             scope.real.pop()
             scope.pop()
-        return self
+            if self.inner or self.parsed:
+                return [self] + sibling_media_queries
+            else:
+                return sibling_media_queries
+        else:
+            return [self]
 
     def raw(self, clean=False):
         """Raw block name
@@ -149,17 +140,22 @@ class Block(Node):
         f = "%(identifier)s%(ws)s{%(nl)s%(proplist)s}%(eb)s"
         out = []
         name = self.name.fmt(fills)
-        if self.parsed and any(p for p in self.parsed if str(type(p)) != "<class 'lesscpy.plib.variable.Variable'>"):
+        if self.parsed and any(
+                p for p in self.parsed
+                if str(type(p)) != "<class 'lesscpy.plib.variable.Variable'>"):
             fills.update({
-                'identifier': name,
-                'proplist': ''.join([p.fmt(fills) for p in self.parsed if p]),
+                'identifier':
+                name,
+                'proplist':
+                ''.join([p.fmt(fills) for p in self.parsed if p]),
             })
             out.append(f % fills)
         if hasattr(self, 'inner'):
             if self.name.subparse and len(self.inner) > 0:  # @media
                 inner = ''.join([p.fmt(fills) for p in self.inner])
                 inner = inner.replace(fills['nl'],
-                                      fills['nl'] + fills['tab']).rstrip(fills['tab'])
+                                      fills['nl'] + fills['tab']).rstrip(
+                                          fills['tab'])
                 if not fills['nl']:
                     inner = inner.strip()
                 fills.update({
@@ -177,8 +173,7 @@ class Block(Node):
         """
         name, inner = self.tokens
         if inner:
-            inner = [u.copy() if u else u
-                     for u in inner]
+            inner = [u.copy() if u else u for u in inner]
         if name:
             name = name.copy()
         return Block([name, inner], 0)
@@ -193,8 +188,7 @@ class Block(Node):
             list (block contents)
         """
         if self.tokens[1]:
-            tokens = [u.copy() if u else u
-                      for u in self.tokens[1]]
+            tokens = [u.copy() if u else u for u in self.tokens[1]]
             out = [p for p in tokens if p]
             utility.rename(out, scope, Block)
             return out
diff --git a/lesscpy/plib/call.py b/lesscpy/plib/call.py
index 2842a3d..f45d04b 100644
--- a/lesscpy/plib/call.py
+++ b/lesscpy/plib/call.py
@@ -13,7 +13,7 @@ try:
     from urllib.parse import quote as urlquote
 except ImportError:
     from urllib import quote as urlquote
-import six
+from six import string_types
 from .node import Node
 import lesscpy.lessc.utility as utility
 import lesscpy.lessc.color as Color
@@ -21,7 +21,6 @@ from lesscpy.lib.colors import lessColors
 
 
 class Call(Node):
-
     """Call node. Node represents a function call.
     All builtin none-color functions are in this node.
     This node attempts calls on built-ins and lets non-builtins
@@ -45,8 +44,10 @@ class Call(Node):
         elif name in ('~', 'e'):
             name = 'escape'
         color = Color.Color()
-        args = [t for t in parsed
-                if not isinstance(t, six.string_types) or t not in '(),']
+        args = [
+            t for t in parsed
+            if not isinstance(t, string_types) or t not in '(),'
+        ]
         if hasattr(self, name):
             try:
                 return getattr(self, name)(*args)
@@ -118,7 +119,7 @@ class Call(Node):
         returns:
             bool
         """
-        return (string in lessColors)
+        return string in lessColors
 
     def isurl(self, string, *args):
         """Is url
@@ -128,15 +129,17 @@ class Call(Node):
             bool
         """
         arg = utility.destring(string)
-        regex = re.compile(r'^(?:http|ftp)s?://'                    # http:// or https://
-                           r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
-                           r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
-                           # localhost...
-                           r'localhost|'
-                           r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'   # ...or ip
-                           # optional port
-                           r'(?::\d+)?'
-                           r'(?:/?|[/?]\S+)$', re.IGNORECASE)
+        regex = re.compile(
+            r'^(?:http|ftp)s?://'  # http:// or https://
+            r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
+            r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
+            # localhost...
+            r'localhost|'
+            r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
+            # optional port
+            r'(?::\d+)?'
+            r'(?:/?|[/?]\S+)$',
+            re.IGNORECASE)
         return regex.match(arg)
 
     def isstring(self, string, *args):
@@ -156,7 +159,7 @@ class Call(Node):
         returns:
             bool
         """
-        return (string in ('when', 'and', 'not'))
+        return string in ('when', 'and', 'not')
 
     def increment(self, value, *args):
         """ Increment function
@@ -185,7 +188,7 @@ class Call(Node):
         returns:
             str
         """
-        if(len(args) <= 1):
+        if len(args) <= 1:
             return 0
         return sum([int(v) for v in args])
 
@@ -197,7 +200,8 @@ class Call(Node):
             str
         """
         n, u = utility.analyze_number(value)
-        return utility.with_unit(int(utility.away_from_zero_round(float(n))), u)
+        return utility.with_unit(
+            int(utility.away_from_zero_round(float(n))), u)
 
     def ceil(self, value, *args):
         """ Ceil number
diff --git a/lesscpy/plib/deferred.py b/lesscpy/plib/deferred.py
index 8ccbbdb..2eb94c8 100644
--- a/lesscpy/plib/deferred.py
+++ b/lesscpy/plib/deferred.py
@@ -11,7 +11,6 @@ from .node import Node
 
 
 class Deferred(Node):
-
     def __init__(self, mixin, args, lineno=0):
         """This node represents mixin calls. The calls
         to these mixins are deferred until the second
@@ -91,8 +90,8 @@ class Deferred(Node):
                     break
 
         if res:
-            store = [t for t in scope.deferred.parsed[
-                -1]] if scope.deferred else False
+            store = [t for t in scope.deferred.parsed[-1]
+                     ] if scope.deferred else False
             tmp_res = []
             for p in res:
                 if p:
@@ -102,7 +101,7 @@ class Deferred(Node):
                         tmp_res.append(p.parse(scope))
             res = tmp_res
             #res = [p.parse(scope, depth=depth+1) for p in res if p]
-            while(any(t for t in res if isinstance(t, Deferred))):
+            while (any(t for t in res if isinstance(t, Deferred))):
                 res = [p.parse(scope) for p in res if p]
             if store:
                 scope.deferred.parsed[-1] = store
diff --git a/lesscpy/plib/expression.py b/lesscpy/plib/expression.py
index 5e5e2ee..f91c5c6 100644
--- a/lesscpy/plib/expression.py
+++ b/lesscpy/plib/expression.py
@@ -16,7 +16,6 @@ from lesscpy.lessc import color
 
 
 class Expression(Node):
-
     """Expression node. Parses all expression except
     color expressions (handled in the color class)
     and unary negation (handled in the NegatedExpression class).
@@ -31,19 +30,18 @@ class Expression(Node):
         returns:
             str
         """
-        assert(len(self.tokens) == 3)
+        assert (len(self.tokens) == 3)
         expr = self.process(self.tokens, scope)
-        A, O, B = [e[0]
-                   if isinstance(e, tuple)
-                   else e
-                   for e in expr
-                   if str(e).strip()]
+        A, O, B = [
+            e[0] if isinstance(e, tuple) else e for e in expr
+            if str(e).strip()
+        ]
         try:
             a, ua = utility.analyze_number(A, 'Illegal element in expression')
             b, ub = utility.analyze_number(B, 'Illegal element in expression')
         except SyntaxError:
             return ' '.join([str(A), str(O), str(B)])
-        if(a is False or b is False):
+        if (a is False or b is False):
             return ' '.join([str(A), str(O), str(B)])
         if ua == 'color' or ub == 'color':
             return color.Color().process((A, O, B))
diff --git a/lesscpy/plib/identifier.py b/lesscpy/plib/identifier.py
index 68c55c7..c2263d7 100644
--- a/lesscpy/plib/identifier.py
+++ b/lesscpy/plib/identifier.py
@@ -14,7 +14,6 @@ from lesscpy.lib import reserved
 
 
 class Identifier(Node):
-
     """Identifier node. Represents block identifier.
     """
 
@@ -30,14 +29,14 @@ class Identifier(Node):
         """
         names = []
         name = []
-        self._subp = (
-            '@media', '@keyframes',
-            '@-moz-keyframes', '@-webkit-keyframes',
-            '@-ms-keyframes'
-        )
+        self._subp = ('@media', '@keyframes', '@-moz-keyframes',
+                      '@-webkit-keyframes', '@-ms-keyframes')
         if self.tokens and hasattr(self.tokens, 'parse'):
-            self.tokens = list(utility.flatten([id.split() + [',']
-                                                for id in self.tokens.parse(scope).split(',')]))
+            self.tokens = list(
+                utility.flatten([
+                    id.split() + [',']
+                    for id in self.tokens.parse(scope).split(',')
+                ]))
             self.tokens.pop()
         if self.tokens and any(hasattr(t, 'parse') for t in self.tokens):
             tmp_tokens = []
@@ -73,15 +72,21 @@ class Identifier(Node):
         # But:      '@media print'  results in [['@media', ' ', 'print']]
         #
         def replace_variables(tokens, scope):
-            return [scope.swap(t)
-                    if (utility.is_variable(t) and not t in reserved.tokens)
-                    else t
-                    for t in tokens]
-        parsed = [list(utility.flatten(replace_variables(part, scope))) for part in parsed]
+            return [
+                scope.swap(t)
+                if (utility.is_variable(t) and not t in reserved.tokens) else t
+                for t in tokens
+            ]
+
+        parsed = [
+            list(utility.flatten(replace_variables(part, scope)))
+            for part in parsed
+        ]
 
-        self.parsed = [[i for i, j in utility.pairwise(part)
-                        if i != ' ' or (j and '?' not in j)]
-                       for part in parsed]
+        self.parsed = [[
+            i for i, j in utility.pairwise(part)
+            if i != ' ' or (j and '?' not in j)
+        ] for part in parsed]
         return self
 
     def root(self, scope, names):
@@ -104,7 +109,9 @@ class Identifier(Node):
                         for part in parent.parsed:
                             if part and part[0] not in self._subp:
                                 filtered_parts.append(part)
-                        permutations = list(utility.permutations_with_replacement(filtered_parts, ampersand_count))
+                        permutations = list(
+                            utility.permutations_with_replacement(
+                                filtered_parts, ampersand_count))
                         for permutation in permutations:
                             parsed = []
                             for name_part in name:
@@ -153,8 +160,7 @@ class Identifier(Node):
             Identifier object
         """
         tokens = ([t for t in self.tokens]
-                  if isinstance(self.tokens, list)
-                  else self.tokens)
+                  if isinstance(self.tokens, list) else self.tokens)
         return Identifier(tokens, 0)
 
     def fmt(self, fills):
@@ -164,7 +170,6 @@ class Identifier(Node):
         returns:
             str (CSS)
         """
-        name = ',$$'.join(''.join(p).strip()
-                          for p in self.parsed)
+        name = ',$$'.join(''.join(p).strip() for p in self.parsed)
         name = re.sub('\?(.)\?', '%(ws)s\\1%(ws)s', name) % fills
         return name.replace('$$', fills['nl']).replace('  ', ' ')
diff --git a/lesscpy/plib/import_.py b/lesscpy/plib/import_.py
index f1a6d15..749b6b9 100644
--- a/lesscpy/plib/import_.py
+++ b/lesscpy/plib/import_.py
@@ -11,7 +11,6 @@ from .node import Node
 
 
 class Import(Node):
-
     """Represents CSS property declaration.
     """
 
diff --git a/lesscpy/plib/keyframe_selector.py b/lesscpy/plib/keyframe_selector.py
index c4677ab..cf2b516 100644
--- a/lesscpy/plib/keyframe_selector.py
+++ b/lesscpy/plib/keyframe_selector.py
@@ -27,8 +27,10 @@ class KeyframeSelector(Node):
         returns:
             self
         """
-        self.keyframe, = [e[0] if isinstance(e, tuple) else e
-                          for e in self.tokens if str(e).strip()]
+        self.keyframe, = [
+            e[0] if isinstance(e, tuple) else e for e in self.tokens
+            if str(e).strip()
+        ]
         self.subparse = False
         return self
 
diff --git a/lesscpy/plib/mixin.py b/lesscpy/plib/mixin.py
index 75ead7e..ffe7e89 100644
--- a/lesscpy/plib/mixin.py
+++ b/lesscpy/plib/mixin.py
@@ -18,7 +18,6 @@ from lesscpy.lessc import utility
 
 
 class Mixin(Node):
-
     """ Mixin Node. Represents callable mixin types.
     """
 
@@ -34,9 +33,10 @@ class Mixin(Node):
         self.name, args, self.guards = self.tokens[0]
         self.args = [a for a in utility.flatten(args) if a]
         self.body = Block([None, self.tokens[1]], 0)
-        self.vars = list(utility.flatten([list(v.values())
-                                          for v in [s['__variables__']
-                                                    for s in scope]]))
+        self.vars = list(
+            utility.flatten([
+                list(v.values()) for v in [s['__variables__'] for s in scope]
+            ]))
         return self
 
     def raw(self):
@@ -56,15 +56,19 @@ class Mixin(Node):
         raises:
             SyntaxError
         """
-        arguments = list(zip(args, [' '] * len(args))) if args and args[0] else None
+        arguments = list(zip(args,
+                             [' '] * len(args))) if args and args[0] else None
         zl = itertools.zip_longest if sys.version_info[
             0] == 3 else itertools.izip_longest
         if self.args:
-            parsed = [v if hasattr(v, 'parse') else v
-                      for v in copy.copy(self.args)]
+            parsed = [
+                v if hasattr(v, 'parse') else v for v in copy.copy(self.args)
+            ]
             args = args if isinstance(args, list) else [args]
-            vars = [self._parse_arg(var, arg, scope)
-                    for arg, var in zl([a for a in args], parsed)]
+            vars = [
+                self._parse_arg(var, arg, scope)
+                for arg, var in zl([a for a in args], parsed)
+            ]
             for var in vars:
                 if var:
                     var.parse(scope)
@@ -125,8 +129,7 @@ class Mixin(Node):
             for g in self.guards:
                 if isinstance(g, list):
                     res = (g[0].parse(scope)
-                           if len(g) == 1
-                           else Expression(g).parse(scope))
+                           if len(g) == 1 else Expression(g).parse(scope))
                     if cor:
                         if res:
                             return True
@@ -147,11 +150,9 @@ class Mixin(Node):
         """
         ret = False
         if args:
-            args = [[a.parse(scope)
-                    if isinstance(a, Expression)
-                    else a for a in arg]
-                    if arg else arg
-                    for arg in args]
+            args = [[
+                a.parse(scope) if isinstance(a, Expression) else a for a in arg
+            ] if arg else arg for arg in args]
         try:
             self.parse_args(args, scope)
         except SyntaxError:
diff --git a/lesscpy/plib/negated_expression.py b/lesscpy/plib/negated_expression.py
index 6140354..60d35d6 100644
--- a/lesscpy/plib/negated_expression.py
+++ b/lesscpy/plib/negated_expression.py
@@ -7,17 +7,16 @@
     See LICENSE for details.
 """
 
-import six
+from six import string_types
 
 from .node import Node
 
 
 class NegatedExpression(Node):
-
     """Expressions preceded by unary negation."""
 
     def parse(self, scope):
         val, = self.process(self.tokens, scope)
-        if isinstance(val, six.string_types):
+        if isinstance(val, string_types):
             return '-' + val
         return -val
diff --git a/lesscpy/plib/node.py b/lesscpy/plib/node.py
index 7a9714f..313a683 100644
--- a/lesscpy/plib/node.py
+++ b/lesscpy/plib/node.py
@@ -11,7 +11,6 @@ from lesscpy.lessc import utility
 
 
 class Node(object):
-
     def __init__(self, tokens, lineno=0):
         """ Base Node
         args:
@@ -43,12 +42,15 @@ class Node(object):
             tokens = list(utility.flatten(tokens))
             done = True
             if any(t for t in tokens if hasattr(t, 'parse')):
-                tokens = [t.parse(scope)
-                          if hasattr(t, 'parse')
-                          else t
-                          for t in tokens]
+                tokens = [
+                    t.parse(scope) if hasattr(t, 'parse') else t
+                    for t in tokens
+                ]
                 done = False
-            if any(t for t in tokens if (utility.is_variable(t)) or str(type(t)) == "<class 'lesscpy.plib.variable.Variable'>"):
+            if any(
+                    t for t in tokens
+                    if (utility.is_variable(t)) or str(type(t)) ==
+                    "<class 'lesscpy.plib.variable.Variable'>"):
                 tokens = self.replace_variables(tokens, scope)
                 done = False
             if done:
diff --git a/lesscpy/plib/property.py b/lesscpy/plib/property.py
index a303877..388f991 100644
--- a/lesscpy/plib/property.py
+++ b/lesscpy/plib/property.py
@@ -12,7 +12,6 @@ from .node import Node
 
 
 class Property(Node):
-
     """Represents CSS property declaration.
     """
 
@@ -48,14 +47,12 @@ class Property(Node):
             list
         """
         if self.property == 'font':
-            style = [''.join(u.expression())
-                     if hasattr(u, 'expression')
-                     else u
-                     for u in style]
+            style = [
+                ''.join(u.expression()) if hasattr(u, 'expression') else u
+                for u in style
+            ]
         else:
-            style = [(u, ' ')
-                     if hasattr(u, 'expression')
-                     else u
+            style = [(u, ' ') if hasattr(u, 'expression') else u
                      for u in style]
         return style
 
@@ -69,14 +66,12 @@ class Property(Node):
         f = "%(tab)s%(property)s:%(ws)s%(style)s%(important)s;%(nl)s"
         imp = ' !important' if self.important else ''
         if fills['nl']:
-            self.parsed = [',%s' % fills['ws']
-                           if p == ','
-                           else p
-                           for p in self.parsed]
-        style = ''.join([p.fmt(fills)
-                         if hasattr(p, 'fmt')
-                         else str(p)
-                         for p in self.parsed])
+            self.parsed = [
+                ',%s' % fills['ws'] if p == ',' else p for p in self.parsed
+            ]
+        style = ''.join([
+            p.fmt(fills) if hasattr(p, 'fmt') else str(p) for p in self.parsed
+        ])
         # IE cannot handle no space after url()
         style = re.sub("(url\([^\)]*\))([^\s,])", "\\1 \\2", style)
         fills.update({
diff --git a/lesscpy/plib/statement.py b/lesscpy/plib/statement.py
index dcb67e9..06540b9 100644
--- a/lesscpy/plib/statement.py
+++ b/lesscpy/plib/statement.py
@@ -12,7 +12,6 @@ from lesscpy.lessc import utility
 
 
 class Statement(Node):
-
     """Represents CSS statement (@import, @charset...)
     """
 
diff --git a/lesscpy/plib/variable.py b/lesscpy/plib/variable.py
index 2ddda8d..7e596a0 100644
--- a/lesscpy/plib/variable.py
+++ b/lesscpy/plib/variable.py
@@ -11,7 +11,6 @@ from .node import Node
 
 
 class Variable(Node):
-
     def parse(self, scope):
         """ Parse function
         args:
diff --git a/lesscpy/scripts/compiler.py b/lesscpy/scripts/compiler.py
index 2d8f890..090011c 100644
--- a/lesscpy/scripts/compiler.py
+++ b/lesscpy/scripts/compiler.py
@@ -55,12 +55,13 @@ def ldirectory(inpath, outpath, args, scope):
             recompile = True
         if recompile:
             print('%s -> %s' % (lf, outf))
-            p = parser.LessParser(yacc_debug=(args.debug),
-                                  lex_optimize=True,
-                                  yacc_optimize=(not args.debug),
-                                  scope=scope,
-                                  tabfile=yacctab,
-                                  verbose=args.verbose)
+            p = parser.LessParser(
+                yacc_debug=(args.debug),
+                lex_optimize=True,
+                yacc_optimize=(not args.debug),
+                scope=scope,
+                tabfile=yacctab,
+                verbose=args.verbose)
             p.parse(filename=lf, debuglevel=0)
             css = f.format(p)
             if not args.dry_run:
@@ -70,54 +71,110 @@ def ldirectory(inpath, outpath, args, scope):
             print('skipping %s, not modified' % lf, file=sys.stderr)
         sys.stdout.flush()
     if args.recurse:
-        [ldirectory(os.path.join(inpath, name), os.path.join(outpath, name), args, scope)
-         for name in os.listdir(inpath)
-            if os.path.isdir(os.path.join(inpath, name)) and
-            not name.startswith('.') and not name == outpath]
+        [
+            ldirectory(
+                os.path.join(inpath, name), os.path.join(outpath, name), args,
+                scope) for name in os.listdir(inpath)
+            if os.path.isdir(os.path.join(inpath, name))
+            and not name.startswith('.') and not name == outpath
+        ]
 
 
 def run():
     """Run compiler
     """
-    aparse = argparse.ArgumentParser(description='LessCss Compiler',
-                                     epilog='<< jtm@robot.is @_o >>')
-    aparse.add_argument('-v', '--version', action='version',
-                        version=VERSION_STR)
-    aparse.add_argument('-I', '--include', action="store", type=str,
-                        help="Included less-files (comma separated)")
-    aparse.add_argument('-V', '--verbose', action="store_true",
-                        default=False, help="Verbose mode")
-    aparse.add_argument('-C', '--dont_create_dirs', action="store_true",
-                        default=False, help="Creates directories when outputing files (lessc non-compatible)")
+    aparse = argparse.ArgumentParser(
+        description='LessCss Compiler', epilog='<< jtm@robot.is @_o >>')
+    aparse.add_argument(
+        '-v', '--version', action='version', version=VERSION_STR)
+    aparse.add_argument(
+        '-I',
+        '--include',
+        action="store",
+        type=str,
+        help="Included less-files (comma separated)")
+    aparse.add_argument(
+        '-V',
+        '--verbose',
+        action="store_true",
+        default=False,
+        help="Verbose mode")
+    aparse.add_argument(
+        '-C',
+        '--dont_create_dirs',
+        action="store_true",
+        default=False,
+        help="Creates directories when outputing files (lessc non-compatible)")
     fgroup = aparse.add_argument_group('Formatting options')
-    fgroup.add_argument('-x', '--minify', action="store_true",
-                        default=False, help="Minify output")
-    fgroup.add_argument('-X', '--xminify', action="store_true",
-                        default=False, help="Minify output, no end of block newlines")
+    fgroup.add_argument(
+        '-x',
+        '--minify',
+        action="store_true",
+        default=False,
+        help="Minify output")
+    fgroup.add_argument(
+        '-X',
+        '--xminify',
+        action="store_true",
+        default=False,
+        help="Minify output, no end of block newlines")
     fgroup.add_argument('-t', '--tabs', help="Use tabs", action="store_true")
     fgroup.add_argument(
-        '-s', '--spaces', help="Number of startline spaces (default 2)", default=2)
-    dgroup = aparse.add_argument_group('Directory options',
-                                       'Compiles all *.less files in directory that '
-                                       'have a newer timestamp than it\'s css file.')
+        '-s',
+        '--spaces',
+        help="Number of startline spaces (default 2)",
+        default=2)
+    dgroup = aparse.add_argument_group(
+        'Directory options', 'Compiles all *.less files in directory that '
+        'have a newer timestamp than it\'s css file.')
     dgroup.add_argument('-o', '--out', action="store", help="Output directory")
     dgroup.add_argument(
-        '-r', '--recurse', action="store_true", help="Recursive into subdirectorys")
+        '-r',
+        '--recurse',
+        action="store_true",
+        help="Recursive into subdirectorys")
+    dgroup.add_argument(
+        '-f',
+        '--force',
+        action="store_true",
+        help="Force recompile on all files")
+    dgroup.add_argument(
+        '-m',
+        '--min-ending',
+        action="store_true",
+        default=False,
+        help="Add '.min' into output filename. eg, name.min.css")
     dgroup.add_argument(
-        '-f', '--force', action="store_true", help="Force recompile on all files")
-    dgroup.add_argument('-m', '--min-ending', action="store_true",
-                        default=False, help="Add '.min' into output filename. eg, name.min.css")
-    dgroup.add_argument('-D', '--dry-run', action="store_true",
-                        default=False, help="Dry run, do not write files")
+        '-D',
+        '--dry-run',
+        action="store_true",
+        default=False,
+        help="Dry run, do not write files")
     group = aparse.add_argument_group('Debugging')
-    group.add_argument('-g', '--debug', action="store_true",
-                       default=False, help="Debugging information")
-    group.add_argument('-S', '--scopemap', action="store_true",
-                       default=False, help="Scopemap")
-    group.add_argument('-L', '--lex-only', action="store_true",
-                       default=False, help="Run lexer on target")
-    group.add_argument('-N', '--no-css', action="store_true",
-                       default=False, help="No css output")
+    group.add_argument(
+        '-g',
+        '--debug',
+        action="store_true",
+        default=False,
+        help="Debugging information")
+    group.add_argument(
+        '-S',
+        '--scopemap',
+        action="store_true",
+        default=False,
+        help="Scopemap")
+    group.add_argument(
+        '-L',
+        '--lex-only',
+        action="store_true",
+        default=False,
+        help="Run lexer on target")
+    group.add_argument(
+        '-N',
+        '--no-css',
+        action="store_true",
+        default=False,
+        help="No css output")
     aparse.add_argument('target', help="less file or directory")
     aparse.add_argument('output', nargs='?', help="output file path")
     args = aparse.parse_args()
@@ -132,7 +189,8 @@ def run():
                 tok = ll.token()
                 if not tok:
                     break
-                if hasattr(tok, "lexer"):  # literals don't have the lexer attribute
+                if hasattr(tok,
+                           "lexer"):  # literals don't have the lexer attribute
                     print(tok, "State:", tok.lexer.lexstate)
                 else:
                     print(tok)
@@ -146,11 +204,12 @@ def run():
         if args.include:
             for u in args.include.split(','):
                 if os.path.exists(u):
-                    p = parser.LessParser(yacc_debug=(args.debug),
-                                          lex_optimize=True,
-                                          yacc_optimize=(not args.debug),
-                                          tabfile=yacctab,
-                                          verbose=args.verbose)
+                    p = parser.LessParser(
+                        yacc_debug=(args.debug),
+                        lex_optimize=True,
+                        yacc_optimize=(not args.debug),
+                        tabfile=yacctab,
+                        verbose=args.verbose)
                     p.parse(filename=u, debuglevel=args.debug)
                     if not scope:
                         scope = p.scope
@@ -168,11 +227,12 @@ def run():
             if args.dry_run:
                 print('Dry run, nothing done.', file=sys.stderr)
         else:
-            p = parser.LessParser(yacc_debug=(args.debug),
-                                  lex_optimize=True,
-                                  yacc_optimize=(not args.debug),
-                                  scope=copy.deepcopy(scope),
-                                  verbose=args.verbose)
+            p = parser.LessParser(
+                yacc_debug=(args.debug),
+                lex_optimize=True,
+                yacc_optimize=(not args.debug),
+                scope=copy.deepcopy(scope),
+                verbose=args.verbose)
             p.parse(filename=args.target, debuglevel=args.debug)
             if args.scopemap:
                 args.no_css = True
@@ -180,7 +240,8 @@ def run():
             if not args.no_css and p:
                 out = f.format(p)
                 if args.output:
-                    if not args.dont_create_dirs and not os.path.exists(os.path.dirname(args.output)):
+                    if not args.dont_create_dirs and not os.path.exists(
+                            os.path.dirname(args.output)):
                         try:
                             os.makedirs(os.path.dirname(args.output))
                         except OSError as exc:  # Guard against race condition
diff --git a/requirements.txt b/requirements.txt
index 4efa1dc..90412f0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1 @@
 ply
-six
diff --git a/setup.py b/setup.py
index cd3cc63..583e36e 100755
--- a/setup.py
+++ b/setup.py
@@ -8,12 +8,13 @@ import codecs
 
 import lesscpy
 
-
 with codecs.open('README.rst', encoding='utf-8') as f:
     long_description = f.read()
 
 with open("requirements.txt", "r") as f:
-    install_requires = [str(req) for req in pkg_resources.parse_requirements(f)]
+    install_requires = [
+        str(req) for req in pkg_resources.parse_requirements(f)
+    ]
 with open("test-requirements.txt", "r") as f:
     test_requires = []
     for line in f.readlines():
@@ -32,10 +33,8 @@ setup(
     url='https://github.com/lesscpy/lesscpy',
     packages=find_packages(exclude=['*test*']),
     package_data={'': ['LICENSE']},
-    entry_points = {
-        'console_scripts' : [
-            'lesscpy = lesscpy.scripts.compiler:run'
-        ]
+    entry_points={
+        'console_scripts': ['lesscpy = lesscpy.scripts.compiler:run']
     },
     install_requires=install_requires,
     tests_require=test_requires,
@@ -49,11 +48,15 @@ setup(
         'License :: OSI Approved :: MIT License',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
-        'Programming Language :: Python :: 2.6',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.3',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.11',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
         'Topic :: Software Development :: Code Generators',
         'Topic :: Software Development :: Pre-processors',
     ],
diff --git a/test-requirements.txt b/test-requirements.txt
index a7052aa..6cce76e 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,3 +2,4 @@
 coverage
 flake8
 nose
+tox
\ No newline at end of file
diff --git a/test/__init__.py b/test/__init__.py
index 8922b7b..454593c 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -6,7 +6,6 @@ import re
 import sys
 import unittest
 
-
 here = os.path.dirname(__file__)
 path = os.path.abspath(here)
 while os.path.dirname(path) != path:
diff --git a/test/core.py b/test/core.py
index 438ec54..a88333a 100644
--- a/test/core.py
+++ b/test/core.py
@@ -15,7 +15,11 @@ class Opt(object):
         self.tabs = True
 
 
-def find_and_load_cases(cls, less_dir, css_dir, less_files=None, css_minimized=True):
+def find_and_load_cases(cls,
+                        less_dir,
+                        css_dir,
+                        less_files=None,
+                        css_minimized=True):
     _less_path = os.path.join(os.path.dirname(__file__), less_dir)
     _css_path = os.path.join(os.path.dirname(__file__), css_dir)
 
@@ -24,14 +28,15 @@ def find_and_load_cases(cls, less_dir, css_dir, less_files=None, css_minimized=T
     else:
         LESS = glob.glob(os.path.join(_less_path, '*.less'))
     for less in LESS:
-        lessf = less.split('.')[0].split('/')[-1]
+        lessf = less.rpartition('.')[0].split('/')[-1]
         css = os.path.join(_css_path, lessf + '.css')
         if css_minimized:
             mincss = os.path.join(_css_path, lessf + '.min.css')
             test_method = create_case((less, css, mincss))
         else:
             test_method = create_case((less, css, None))
-        test_method.__name__ = 'test_%s' % "_".join(reversed(os.path.basename(less).split('.')))
+        test_method.__name__ = 'test_%s' % "_".join(
+            reversed(os.path.basename(less).split('.')))
         setattr(cls, test_method.__name__, test_method)
 
 
@@ -48,13 +53,13 @@ def create_case(args):
             with open(cssf) as cssf:
                 for line in cssf.readlines():
                     if i >= pl:
-                        self.fail(
-                            "%s: result has less lines (%d < %d)" % (cssf, i, pl))
+                        self.fail("%s: result has less lines (%d < %d)" %
+                                  (cssf, i, pl))
                     line = line.rstrip()
                     if not line:
                         continue
-                    self.assertEqual(
-                        line, pout[i], '%s: Line %d' % (cssf, i + 1))
+                    self.assertEqual(line, pout[i],
+                                     '%s: Line %d' % (cssf, i + 1))
                     i += 1
             if pl > i and i:
                 self.fail(
@@ -74,14 +79,15 @@ def create_case(args):
                 with open(minf) as cssf:
                     for line in cssf.readlines():
                         if i >= ml:
-                            self.fail(
-                                "%s: result has less lines (%d < %d)" % (minf, i, ml))
-                        self.assertEqual(
-                            line.rstrip(), mout[i], '%s: Line %d' % (minf, i + 1))
+                            self.fail("%s: result has less lines (%d < %d)" %
+                                      (minf, i, ml))
+                        self.assertEqual(line.rstrip(), mout[i],
+                                         '%s: Line %d' % (minf, i + 1))
                         i += 1
                 if ml > i and i:
                     self.fail(
                         "%s: result has more lines (%d > %d)" % (minf, i, ml))
             else:
                 self.fail("%s not found..." % minf)
+
     return do_case_expected
diff --git a/test/css/css-variables.css b/test/css/css-variables.css
new file mode 100644
index 0000000..9f91915
--- /dev/null
+++ b/test/css/css-variables.css
@@ -0,0 +1,17 @@
+:root {
+	--bg: #e0e0e0;
+}
+p {
+	--bg: white;
+}
+* {
+	background: var(--bg);
+	color: var(--fg,var(--text-fg,black));
+}
+div {
+	--bg: red;
+}
+div span {
+	--bg: purple;
+	background: var(--bg);
+}
diff --git a/test/css/css-variables.min.css b/test/css/css-variables.min.css
new file mode 100644
index 0000000..9e8fa7e
--- /dev/null
+++ b/test/css/css-variables.min.css
@@ -0,0 +1,5 @@
+:root{--bg:#e0e0e0;}
+p{--bg:white;}
+*{background:var(--bg);color:var(--fg,var(--text-fg,black));}
+div{--bg:red;}
+div span{--bg:purple;background:var(--bg);}
diff --git a/test/css/issues/issue61.css b/test/css/issues/issue61.css
new file mode 100644
index 0000000..5e4711c
--- /dev/null
+++ b/test/css/issues/issue61.css
@@ -0,0 +1,10 @@
+@media screen and (max-width:460px) {
+	h1.title {
+		background: url("image_460.jpg");
+	}
+}
+@media screen and (min-width:461px) {
+	h1.title {
+		background: url("image_740.jpg");
+	}
+}
diff --git a/test/css/issues/issue65.css b/test/css/issues/issue65.css
new file mode 100644
index 0000000..5c4a148
--- /dev/null
+++ b/test/css/issues/issue65.css
@@ -0,0 +1,10 @@
+@media (max-width:400px) {
+	body {
+		color: green;
+	}
+}
+@media (max-width:30px) {
+	body h3 .before {
+		color: red;
+	}
+}
diff --git a/test/css/media-nested-2.css b/test/css/media-nested-2.css
new file mode 100644
index 0000000..94417d2
--- /dev/null
+++ b/test/css/media-nested-2.css
@@ -0,0 +1,23 @@
+@media screen {
+	div span p {
+		color: auto;
+	}
+}
+@media screen and (max-width:100px) and (min-width:50px) and print {
+	p {
+		color: auto;
+	}
+}
+@media (width:400px) {
+	.one {
+		font-size: 1.2em;
+	}
+	.one .sub {
+		background: red;
+	}
+}
+@media (width:400px) and print and (color) {
+	.one {
+		color: blue;
+	}
+}
diff --git a/test/css/media-nested-2.min.css b/test/css/media-nested-2.min.css
new file mode 100644
index 0000000..538aa99
--- /dev/null
+++ b/test/css/media-nested-2.min.css
@@ -0,0 +1,5 @@
+@media screen{div span p{color:auto;}}
+@media screen and (max-width:100px) and (min-width:50px) and print{p{color:auto;}}
+@media (width:400px){.one{font-size:1.2em;}
+.one .sub{background:red;}}
+@media (width:400px) and print and (color){.one{color:blue;}}
diff --git a/test/css/media-nested.css b/test/css/media-nested.css
new file mode 100644
index 0000000..5472dc1
--- /dev/null
+++ b/test/css/media-nested.css
@@ -0,0 +1,43 @@
+@media (width:768px) {
+	.lead {
+		font-size: 21px;
+	}
+}
+@media (width:400px) {
+	.one {
+		font-size: 1.2em;
+	}
+}
+@media (width:400px) and print and (color) {
+	.one {
+		color: blue;
+	}
+}
+.two {
+	width: 100px;
+}
+@media (width:400px) {
+	.two {
+		font-size: 1.2em;
+	}
+}
+@media print and (color) {
+	.two {
+		color: blue;
+	}
+}
+@media print {
+	.visible {
+		color: green;
+	}
+}
+@media screen {
+	.visible.visible-sm {
+		color: green;
+	}
+}
+@media (max-width:10px) {
+	.navbar .form {
+		margin-bottom: 5px;
+	}
+}
diff --git a/test/css/media-nested.min.css b/test/css/media-nested.min.css
new file mode 100644
index 0000000..9f353b9
--- /dev/null
+++ b/test/css/media-nested.min.css
@@ -0,0 +1,9 @@
+@media (width:768px){.lead{font-size:21px;}}
+@media (width:400px){.one{font-size:1.2em;}}
+@media (width:400px) and print and (color){.one{color:blue;}}
+.two{width:100px;}
+@media (width:400px){.two{font-size:1.2em;}}
+@media print and (color){.two{color:blue;}}
+@media print{.visible{color:green;}}
+@media screen{.visible.visible-sm{color:green;}}
+@media (max-width:10px){.navbar .form{margin-bottom:5px;}}
diff --git a/test/css/media.css b/test/css/media.css
index afebe5e..f078401 100644
--- a/test/css/media.css
+++ b/test/css/media.css
@@ -53,46 +53,3 @@
 @media only screen and (min--moz-device-pixel-ratio:2.5),only screen and (-o-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2),only screen and (min-resolution:192dpi),only screen and (min-resolution:2dppx) {
 	background-size: 10px;
 }
-@media (width:768px) {
-	.lead {
-		font-size: 21px;
-	}
-}
-@media (width:400px) {
-	.one {
-		font-size: 1.2em;
-	}
-}
-@media (width:400px) and print and (color) {
-	.one {
-		color: blue;
-	}
-}
-.two {
-	width: 100px;
-}
-@media (width:400px) {
-	.two {
-		font-size: 1.2em;
-	}
-}
-@media print and (color) {
-	.two {
-		color: blue;
-	}
-}
-@media print {
-	.visible {
-		color: green;
-	}
-}
-@media screen {
-	.visible.visible-sm {
-		color: green;
-	}
-}
-@media (max-width:10px) {
-	.navbar .form {
-		margin-bottom: 5px;
-	}
-}
diff --git a/test/css/media.min.css b/test/css/media.min.css
index aa94486..bd06df8 100644
--- a/test/css/media.min.css
+++ b/test/css/media.min.css
@@ -10,12 +10,3 @@ body{max-width:35em;margin:0 auto;}}
 @media (min-width:12px){body{margin:0 auto;}}
 @media (width:767px){.visible-xs{display:block;}}
 @media only screen and (min--moz-device-pixel-ratio:2.5),only screen and (-o-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2),only screen and (min-resolution:192dpi),only screen and (min-resolution:2dppx){background-size:10px;}
-@media (width:768px){.lead{font-size:21px;}}
-@media (width:400px){.one{font-size:1.2em;}}
-@media (width:400px) and print and (color){.one{color:blue;}}
-.two{width:100px;}
-@media (width:400px){.two{font-size:1.2em;}}
-@media print and (color){.two{color:blue;}}
-@media print{.visible{color:green;}}
-@media screen{.visible.visible-sm{color:green;}}
-@media (max-width:10px){.navbar .form{margin-bottom:5px;}}
diff --git a/test/less/css-variables.less b/test/less/css-variables.less
new file mode 100644
index 0000000..dcd0c18
--- /dev/null
+++ b/test/less/css-variables.less
@@ -0,0 +1,21 @@
+:root {
+  --bg: #e0e0e0;
+}
+
+p {
+  --bg: white;
+}
+
+* {
+  background: var(--bg);
+  color: var(--fg, var(--text-fg, black));
+}
+
+div {
+  --bg: red;
+
+  span {
+    --bg: purple;
+    background: var(--bg);
+  }
+}
diff --git a/test/less/issues/issue61.less b/test/less/issues/issue61.less
new file mode 100644
index 0000000..e142fdc
--- /dev/null
+++ b/test/less/issues/issue61.less
@@ -0,0 +1,10 @@
+h1 {
+	&.title {
+		@media screen and (max-width: 460px) {
+			background: url("image_460.jpg");
+		}
+		@media screen and (min-width: 461px) {
+			background: url("image_740.jpg");
+		}
+	}
+}
diff --git a/test/less/issues/issue65.less b/test/less/issues/issue65.less
new file mode 100644
index 0000000..98c5fea
--- /dev/null
+++ b/test/less/issues/issue65.less
@@ -0,0 +1,12 @@
+body {
+    @media (max-width: 400px) {
+        color:green;
+    }
+    h3 {
+        .before {
+            @media (max-width: 30px) {
+                color: red;
+            }
+        }
+    }
+}
diff --git a/test/less/media-nested-2.less b/test/less/media-nested-2.less
new file mode 100644
index 0000000..4be2c6f
--- /dev/null
+++ b/test/less/media-nested-2.less
@@ -0,0 +1,35 @@
+/* Media queries nested deep in the tree */
+div {
+  span {
+    p {
+      @media screen {
+        color: auto;
+      }
+    }
+  }
+}
+/* Streak of media queries to be merged */
+@media screen {
+  @media (max-width: 100px) {
+    @media (min-width: 50px) {
+      @media print {
+        p {
+          color: auto;
+        }
+      }
+    }
+  }
+}
+/* Interaction of media queries with properties, smaller blocks, and other
+   media queries at once */
+.one {
+  @media (width: 400px){
+    font-size: 1.2em;
+    .sub {
+      background: red;
+    }
+    @media print and (color) {
+      color: blue;
+    }
+  }
+}
diff --git a/test/less/media-nested.less b/test/less/media-nested.less
new file mode 100644
index 0000000..08f0720
--- /dev/null
+++ b/test/less/media-nested.less
@@ -0,0 +1,42 @@
+/*
+    Nested media queries
+*/
+.lead {
+  @media (width: 768px) {
+    font-size: 21px;
+  }
+}
+.one {
+  @media (width: 400px){
+    font-size: 1.2em;
+    @media print and (color) {
+      color: blue;
+    }
+  }
+}
+.two {
+  @media (width: 400px){
+    font-size: 1.2em;
+  }
+  @media print and (color) {
+    color: blue;
+  }
+  width: 100px;
+}
+.visible {
+  @media print {
+    color: green;
+  }
+  &.visible-sm {
+    @media screen {
+      color: green;
+    }
+  }
+}
+.navbar {
+  .form {
+    @media (max-width: 10px) {
+      margin-bottom: 5px;
+    }
+  }
+}
diff --git a/test/less/media.less b/test/less/media.less
index 3e4c846..ea09670 100644
--- a/test/less/media.less
+++ b/test/less/media.less
@@ -70,45 +70,3 @@
   only screen and (                min-resolution: 2dppx) {
     background-size: 10px;
 }
-/*
-    Nested media queries
-*/
-.lead {
-  @media (width: 768px) {
-    font-size: 21px;
-  }
-}
-.one {
-  @media (width: 400px){
-    font-size: 1.2em;
-    @media print and (color) {
-      color: blue;
-    }
-  }
-}
-.two {
-  @media (width: 400px){
-    font-size: 1.2em;
-  }
-  @media print and (color) {
-    color: blue;
-  }
-  width: 100px;
-}
-.visible {
-  @media print {
-    color: green;
-  }
-  &.visible-sm {
-    @media screen {
-      color: green;
-    }
-  }
-}
-.navbar {
-  .form {
-    @media (max-width: 10px) {
-      margin-bottom: 5px;
-    }
-  }
-}
diff --git a/test/test_bootstrap3.py b/test/test_bootstrap3.py
index 2451456..f153eb5 100644
--- a/test/test_bootstrap3.py
+++ b/test/test_bootstrap3.py
@@ -15,12 +15,14 @@ class Bootstrap3ThemeTestCase(unittest.TestCase):
     pass
 
 
-find_and_load_cases(Bootstrap3TestCase,
-                    less_dir='bootstrap3/less',
-                    less_files=['bootstrap'],
-                    css_dir='bootstrap3/css')
-
-find_and_load_cases(Bootstrap3ThemeTestCase,
-                    less_dir='bootstrap3/less',
-                    less_files=['theme'],
-                    css_dir='bootstrap3/css')
+find_and_load_cases(
+    Bootstrap3TestCase,
+    less_dir='bootstrap3/less',
+    less_files=['bootstrap'],
+    css_dir='bootstrap3/css')
+
+find_and_load_cases(
+    Bootstrap3ThemeTestCase,
+    less_dir='bootstrap3/less',
+    less_files=['theme'],
+    css_dir='bootstrap3/css')
diff --git a/test/test_color.py b/test/test_color.py
index 4ae87bc..6daacb1 100644
--- a/test/test_color.py
+++ b/test/test_color.py
@@ -133,7 +133,6 @@ class TestLessColor(unittest.TestCase):
             ('#29332f', '40%', '#174533'),
             ('#29332f', '60%', '#0d4f35'),
             ('#29332f', '100%', '#005c37'),
-
         ]:
             self.assertEqual(test(c, p), v, v)
 
@@ -156,7 +155,6 @@ class TestLessColor(unittest.TestCase):
             ('#29332f', '40%', '#2e2e2e'),
             ('#29332f', '60%', '#2e2e2e'),
             ('#29332f', '100%', '#2e2e2e'),
-
         ]:
             self.assertEqual(test(c, p), v, v)
 
@@ -179,6 +177,5 @@ class TestLessColor(unittest.TestCase):
             ('#29332f', '40%', '#293033'),
             ('#29332f', '60%', '#292d33'),
             ('#29332f', '100%', '#2c2933'),
-
         ]:
             self.assertEqual(test(c, p), v, v)
diff --git a/test/test_expression.py b/test/test_expression.py
index be70bf7..3434b7c 100644
--- a/test/test_expression.py
+++ b/test/test_expression.py
@@ -14,8 +14,8 @@ class TestExpression(unittest.TestCase):
             ['2.0', '+', '2', '4'],
             ['2', '+', '2.0', '4'],
             ['2.0', '+', '2.0', '4'],
-            [('2.0',), '+', '2.0', '4'],
-            [('2.0',), '+', ('2.0',), '4'],
+            [('2.0', ), '+', '2.0', '4'],
+            [('2.0', ), '+', ('2.0', ), '4'],
             ['0px', '+', '0', '0'],
             ['2px', '+', '2', '4px'],
             ['2.0px', '+', '2', '4px'],
diff --git a/test/test_identifier.py b/test/test_identifier.py
index 14d5157..d729cc4 100644
--- a/test/test_identifier.py
+++ b/test/test_identifier.py
@@ -5,7 +5,6 @@ from lesscpy.plib.identifier import Identifier
 
 
 class TestIdentifier(unittest.TestCase):
-
     def test_basic(self):
         fl = {'ws': ' ', 'nl': '\n'}
         for i in [
@@ -77,21 +76,23 @@ class TestIdentifier(unittest.TestCase):
         sc.push()
         sc.current = Identifier(['.c', ',', '.d'], 0).parse(sc)
         id = Identifier(['.deep'], 0)
-        self.assertEqual(id.parse(sc).fmt(fl), '.a .next .c .deep,\n'
-                         '.b .next .c .deep,\n'
-                         '.a .next .d .deep,\n'
-                         '.b .next .d .deep')
-        self.assertEqual(id.raw(), '.a% %.next% %.c% %.deep%'
-                                   '.b% %.next% %.c% %.deep%'
-                                   '.a% %.next% %.d% %.deep%'
-                                   '.b% %.next% %.d% %.deep')
+        self.assertEqual(
+            id.parse(sc).fmt(fl), '.a .next .c .deep,\n'
+            '.b .next .c .deep,\n'
+            '.a .next .d .deep,\n'
+            '.b .next .d .deep')
+        self.assertEqual(
+            id.raw(), '.a% %.next% %.c% %.deep%'
+            '.b% %.next% %.c% %.deep%'
+            '.a% %.next% %.d% %.deep%'
+            '.b% %.next% %.d% %.deep')
 
     def test_media(self):
         fl = {'ws': ' ', 'nl': '\n'}
         sc = Scope()
         sc.push()
-        sc.current = Identifier(
-            ['@media', ' ', 'screen', ',', 'projection'], 0).parse(sc)
+        sc.current = Identifier(['@media', ' ', 'screen', ',', 'projection'],
+                                0).parse(sc)
         self.assertEqual(sc.current.fmt(fl), '@media screen,projection')
         for i in [
             (['html'], 'html'),
diff --git a/test/test_issues.py b/test/test_issues.py
index 46e51e7..91719f8 100644
--- a/test/test_issues.py
+++ b/test/test_issues.py
@@ -10,7 +10,8 @@ class IssuesTestCase(unittest.TestCase):
     pass
 
 
-find_and_load_cases(IssuesTestCase,
-                    less_dir='less/issues',
-                    css_dir='css/issues',
-                    css_minimized=False)
+find_and_load_cases(
+    IssuesTestCase,
+    less_dir='less/issues',
+    css_dir='css/issues',
+    css_minimized=False)
diff --git a/test/test_less.py b/test/test_less.py
index b5dcc1c..ef7dfc0 100644
--- a/test/test_less.py
+++ b/test/test_less.py
@@ -10,6 +10,4 @@ class LessTestCase(unittest.TestCase):
     pass
 
 
-find_and_load_cases(LessTestCase,
-                    less_dir='less',
-                    css_dir='css')
+find_and_load_cases(LessTestCase, less_dir='less', css_dir='css')
diff --git a/test/test_pycompile.py b/test/test_pycompile.py
index 195b86c..2ed54aa 100644
--- a/test/test_pycompile.py
+++ b/test/test_pycompile.py
@@ -42,4 +42,5 @@ class TestCompileFunction(unittest.TestCase):
 
         def fail_func():
             compile(StringIO("a }"), minify=True)
+
         self.assertRaises(CompilationError, fail_func)
diff --git a/test/test_utility.py b/test/test_utility.py
index 5c9d673..3ca4bcb 100644
--- a/test/test_utility.py
+++ b/test/test_utility.py
@@ -88,7 +88,7 @@ class TestUtility(unittest.TestCase):
         self.assertEqual('0.6px', test(.6, 'px'))
         self.assertEqual('1', test(1))
         self.assertEqual('1', test(1, None))
-        self.assertEqual('1', test(1,))
+        self.assertEqual('1', test(1, ))
 
     def test_convergent_round(self):
         test = utility.convergent_round
diff --git a/tox.ini b/tox.ini
index 8c9b39f..c4c5627 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,6 @@
 [tox]
-envlist = py27,py33,p36
+envlist = py37,py38,py39,py3.10,py3.11,pypy3
+skip_missing_interpreters = True
 
 [testenv]
 deps = -r{toxinidir}/test-requirements.txt

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.15.1.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.15.1.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.15.1.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.15.1.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.15.1.egg-info/top_level.txt

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.13.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.13.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.13.0.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.13.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/lesscpy-0.13.0.egg-info/top_level.txt

Control files: lines which differ (wdiff format)

  • Depends: python3-ply, python3-six, python3:any

More details

Full run details