From d8cd1fa96869d573f9cfc6b8a294ae7c0c567c0c Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 12:47:37 +0200 Subject: [PATCH 1/8] Don't switch operands for non-implemented operators. Bot __truediv__(int, float) and __add__(int, float) raise NotImplemented, but switching only helps in the case of addition. Division isn't commutative so simply cast the int to float. Due to this, lesscpy now generates the same result as lessc in the modified test case. --- lesscpy/plib/expression.py | 4 +++- lesscpy/test/css/calls.css | 2 +- lesscpy/test/css/calls.min.css | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lesscpy/plib/expression.py b/lesscpy/plib/expression.py index 6bd27ce..5b45374 100644 --- a/lesscpy/plib/expression.py +++ b/lesscpy/plib/expression.py @@ -125,7 +125,9 @@ class Expression(Node): else: ret = getattr(vala, operation)(valb) if ret is NotImplemented: - ret = getattr(valb, operation)(vala) + # __truediv__(int, float) isn't implemented, but __truediv__(float, float) is. + # __add__(int, float) is similar. Simply cast vala to float: + ret = getattr(float(vala), operation)(valb) if oper in '+-*/': try: if int(ret) == ret: diff --git a/lesscpy/test/css/calls.css b/lesscpy/test/css/calls.css index d0f6aef..d0f88b0 100644 --- a/lesscpy/test/css/calls.css +++ b/lesscpy/test/css/calls.css @@ -19,7 +19,7 @@ format-url-encode: 'red is %23ff0000'; } #more { - width: 1px; + width: 2px; height: 1px; top: 50%; } diff --git a/lesscpy/test/css/calls.min.css b/lesscpy/test/css/calls.min.css index f5cb8c2..0dc07ba 100644 --- a/lesscpy/test/css/calls.min.css +++ b/lesscpy/test/css/calls.min.css @@ -1,6 +1,6 @@ #standard{width:16;height:undefined("self");border-width:5;variable:11;decrement:9;rounded:11;roundedpx:3px;} #escapes{escaped:-Some::weird(#thing, y);escaped1:-Some::weird(#thing, z);eformat:rgb(32, 128, 64);} #format{format:"rgb(32, 128, 64)";format-string:"hello world";format-multiple:"hello earth 2";format-url-encode:'red is %23ff0000';} -#more{width:1px;height:1px;top:50%;} +#more{width:2px;height:1px;top:50%;} #colors{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#333333', GradientType=1);} a{background-image:linear-gradient(top,#ffffff,#333333);background-image:linear-gradient(top,#a6a6a6,#000000);} From 57a9891d77d6480e4bbe39ec1f84ea13ca802efe Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 13:22:30 +0200 Subject: [PATCH 2/8] Revert "Use precise grid margin values" This reverts commit 2b2a41751e97fa14f6c37ab5aea6598645e2eb4a. --- lesscpy/test/css/grid.css | 4 ++-- lesscpy/test/css/grid.min.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lesscpy/test/css/grid.css b/lesscpy/test/css/grid.css index ba266f1..67674c2 100644 --- a/lesscpy/test/css/grid.css +++ b/lesscpy/test/css/grid.css @@ -124,13 +124,13 @@ margin-left: 59.1; } .offset3 { - margin-left: 87.8; + margin-left: 87.80000000000001; } .offset4 { margin-left: 116.5; } .offset5 { - margin-left: 145.2; + margin-left: 145.20000000000002; } .offset6 { margin-left: 173.9; diff --git a/lesscpy/test/css/grid.min.css b/lesscpy/test/css/grid.min.css index 215ccc7..06c58e4 100644 --- a/lesscpy/test/css/grid.min.css +++ b/lesscpy/test/css/grid.min.css @@ -39,9 +39,9 @@ .span12,.container{width:342.7;} .offset1{margin-left:30.4;} .offset2{margin-left:59.1;} -.offset3{margin-left:87.8;} +.offset3{margin-left:87.80000000000001;} .offset4{margin-left:116.5;} -.offset5{margin-left:145.2;} +.offset5{margin-left:145.20000000000002;} .offset6{margin-left:173.9;} .offset7{margin-left:202.6;} .offset8{margin-left:231.3;} From 833d887789e48219e00bdf8019cbbd68b7eb6bc3 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 15:12:03 +0200 Subject: [PATCH 3/8] Really fix rounding errors The result of str(float) was changed with Python3: % python Python 2.7.3 (default, Apr 14 2012, 08:58:41) [GCC] on linux2 >>> repr(1.1*1.1) '1.2100000000000002' >>> str(1.1*1.1) '1.21' % python3 Python 3.3.0 (default, Oct 01 2012, 09:13:30) [GCC] on linux >>> repr(1.1*1.1) '1.2100000000000002' >>> str(1.1*1.1) '1.2100000000000002' Thus, instead of rounding the resulting CSS, don't use str() but rather repr() to return the correct value in with_unit(). --- lesscpy/plib/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesscpy/plib/expression.py b/lesscpy/plib/expression.py index 5b45374..d758db1 100644 --- a/lesscpy/plib/expression.py +++ b/lesscpy/plib/expression.py @@ -96,7 +96,7 @@ class Expression(Node): return str(val) + ua elif ub: return str(val) + ub - return str(val) + return repr(val) def operate(self, vala, valb, oper): """Perform operation From 3e8c4b71df6515856d69507f9ec286b41dfe8a04 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 17:43:35 +0200 Subject: [PATCH 4/8] Use CSS class '.darken' for darken(), not '.lighten' --- lesscpy/test/css/colors.css | 2 +- lesscpy/test/css/colors.min.css | 2 +- lesscpy/test/less/colors.less | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lesscpy/test/css/colors.css b/lesscpy/test/css/colors.css index 1f0d87d..78030de 100644 --- a/lesscpy/test/css/colors.css +++ b/lesscpy/test/css/colors.css @@ -91,7 +91,7 @@ color: #c1cdc8; color: #ffffff; } -.lighten { +.darken { color: #525252; color: #3b3b3b; color: #222222; diff --git a/lesscpy/test/css/colors.min.css b/lesscpy/test/css/colors.min.css index 839529d..b9ea18f 100644 --- a/lesscpy/test/css/colors.min.css +++ b/lesscpy/test/css/colors.min.css @@ -13,7 +13,7 @@ .saturate{color:#565454;color:#5e4c4c;color:#664444;color:#773333;color:#882222;color:#aa0000;color:#000000;color:#000000;color:#ffffff;color:#ffffff;color:#29332f;color:#243830;color:#203c31;color:#174533;color:#0d4f35;color:#005c37;} .desaturate{color:#555555;color:#555555;color:#555555;color:#555555;color:#555555;color:#555555;color:#000000;color:#000000;color:#ffffff;color:#ffffff;color:#29332f;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;} .lighten{color:#585858;color:#6f6f6f;color:#888888;color:#bbbbbb;color:#eeeeee;color:#ffffff;color:#ffffff;color:#000000;color:#ffffff;color:#ffffff;color:#2b3632;color:#404f49;color:#566c63;color:#88a096;color:#c1cdc8;color:#ffffff;} -.lighten{color:#525252;color:#3b3b3b;color:#222222;color:#000000;color:#000000;color:#000000;color:#000000;color:#000000;color:#000000;color:#ffffff;color:#27302c;color:#121715;color:#000000;color:#000000;color:#000000;color:#000000;} +.darken{color:#525252;color:#3b3b3b;color:#222222;color:#000000;color:#000000;color:#000000;color:#000000;color:#000000;color:#000000;color:#ffffff;color:#27302c;color:#121715;color:#000000;color:#000000;color:#000000;color:#000000;} .spin{color:#555555;color:#555555;color:#555555;color:#555555;color:#555555;color:#555555;color:#000000;color:#000000;color:#ffffff;color:#ffffff;color:#29332f;color:#29332d;color:#293332;color:#2a3329;color:#292d33;color:#2c2933;} .grayscale{color:#000000;color:#000000;color:#ffffff;color:#ffffff;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;color:#2e2e2e;} .mix{color:#7f007f;color:#7f7f7f;color:#7f9055;color:#3f00bf;color:#ff0000;color:#0000ff;} diff --git a/lesscpy/test/less/colors.less b/lesscpy/test/less/colors.less index 89b0fa2..14dccf2 100644 --- a/lesscpy/test/less/colors.less +++ b/lesscpy/test/less/colors.less @@ -98,7 +98,7 @@ color: lighten(#29332f, 60%); color: lighten(#29332f, 100%); } -.lighten { +.darken { color: darken(#555, 1%); color: darken(#555, 10%); color: darken(#555, 20%); From 7a65e81fa7b9ce0b0997b5fd8b956be36faaf3a7 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 17:44:31 +0200 Subject: [PATCH 5/8] Add utility function convergent_round Mimics Python3's changed rounding behavior: "The round() function rounding strategy and return type have changed. Exact halfway cases are now rounded to the nearest even result instead of away from zero. (For example, round(2.5) now returns 2 rather than 3.) round(x[, n]) now delegates to x.__round__([n]) instead of always returning a float. It generally returns an integer when called with a single argument and a value of the same type as x when called with two arguments." --- lesscpy/lessc/utility.py | 22 ++++++++++++++++++++++ lesscpy/test/testutility.py | 14 ++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lesscpy/lessc/utility.py b/lesscpy/lessc/utility.py index 33977a4..73f5361 100644 --- a/lesscpy/lessc/utility.py +++ b/lesscpy/lessc/utility.py @@ -11,6 +11,7 @@ from __future__ import print_function import collections +import math import re @@ -243,3 +244,24 @@ def split_unit(value): """ r = re.search('^(\-?[\d\.]+)(.*)$', str(value)) return r.groups() if r else ('', '') + + +def convergent_round(value, ndigits=0): + """Convergent rounding. + + Round to neareas even, similar to Python3's round() method. + """ + if sys.version_info[0] < 3: + if value < 0.0: + return -convergent_round(-value) + + epsilon = 0.0000001 + integral_part, _ = divmod(value, 1) + + if abs(value - (integral_part + 0.5)) < epsilon: + if integral_part % 2.0 < epsilon: + return integral_part + else: + nearest_even = integral_part + 0.5 + return math.ceil(nearest_even) + return round(value, ndigits) diff --git a/lesscpy/test/testutility.py b/lesscpy/test/testutility.py index 3bf8ea1..3a2a992 100644 --- a/lesscpy/test/testutility.py +++ b/lesscpy/test/testutility.py @@ -92,6 +92,20 @@ class TestUtility(unittest.TestCase): self.assertEqual('1', test(1, None)) self.assertEqual('1', test(1,)) + def test_convergent_round(self): + test = utility.convergent_round + self.assertEqual(-4, test(-4.5)) + self.assertEqual(-4, test(-3.5)) + self.assertEqual(-2, test(-2.5)) + self.assertEqual(-2, test(-1.5)) + self.assertEqual(0, test(-0.5)) + self.assertEqual(0, test(0.5)) + self.assertEqual(2, test(1.5)) + self.assertEqual(2, test(2.5)) + self.assertEqual(3.0, test(10.0 / 3, 0)) + self.assertEqual(4, test(3.5)) + self.assertEqual(4, test(4.5)) + if __name__ == '__main__': unittest.main() From 8da243d2b83e9397e4d1a305e2902a09fca37957 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 17:56:36 +0200 Subject: [PATCH 6/8] Use convergent_round everywhere except for round() LESS function. The LESS function round uses away-from-zero rounding. --- lesscpy/lessc/color.py | 12 ++++++------ lesscpy/test/css/calls.css | 2 ++ lesscpy/test/css/calls.min.css | 2 +- lesscpy/test/less/calls.less | 2 ++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lesscpy/lessc/color.py b/lesscpy/lessc/color.py index c0da7fa..1a392ed 100644 --- a/lesscpy/lessc/color.py +++ b/lesscpy/lessc/color.py @@ -105,7 +105,7 @@ class Color(): if isinstance(s, str): s = int(s.strip('%')) rgb = colorsys.hls_to_rgb(int(h) / 360.0, l / 100.0, s / 100.0) - color = (round(c * 255) for c in rgb) + color = (utility.convergent_round(c * 255) for c in rgb) return self._rgbatohex(color) raise ValueError('Illegal color values') @@ -123,8 +123,8 @@ class Color(): if isinstance(s, str): s = int(s.strip('%')) rgb = colorsys.hls_to_rgb(int(h) / 360.0, l / 100.0, s / 100.0) - color = [float(round(c * 255)) for c in rgb] - color.append(round(float(a[:-1]) / 100.0, 2)) + color = [float(utility.convergent_round(c * 255)) for c in rgb] + color.append(utility.convergent_round(float(a[:-1]) / 100.0, 2)) return "rgba(%s,%s,%s,%s)" % tuple(color) raise ValueError('Illegal color values') @@ -139,7 +139,7 @@ class Color(): """ if color: h, l, s = self._hextohls(color) - return round(h * 360.0, 3) + return utility.convergent_round(h * 360.0, 3) raise ValueError('Illegal color values') def saturation(self, color, *args): @@ -260,7 +260,7 @@ class Color(): h = ((h * 360.0) + degree) % 360.0 h = 360.0 + h if h < 0 else h rgb = colorsys.hls_to_rgb(h / 360.0, l, s) - color = (round(c * 255) for c in rgb) + color = (utility.convergent_round(c * 255) for c in rgb) return self._rgbatohex(color) raise ValueError('Illegal color values') @@ -364,5 +364,5 @@ class Color(): hls = list(self._hextohls(color)) hls[idx] = self._clamp(getattr(hls[idx], op)(diff / 100.0)) rgb = colorsys.hls_to_rgb(*hls) - color = (round(c * 255) for c in rgb) + color = (utility.convergent_round(c * 255) for c in rgb) return self._rgbatohex(color) diff --git a/lesscpy/test/css/calls.css b/lesscpy/test/css/calls.css index d0f88b0..cde5394 100644 --- a/lesscpy/test/css/calls.css +++ b/lesscpy/test/css/calls.css @@ -6,6 +6,8 @@ decrement: 9; rounded: 11; roundedpx: 3px; + round25: 3; + round15: 2; } #escapes { escaped: -Some::weird(#thing, y); diff --git a/lesscpy/test/css/calls.min.css b/lesscpy/test/css/calls.min.css index 0dc07ba..c896d45 100644 --- a/lesscpy/test/css/calls.min.css +++ b/lesscpy/test/css/calls.min.css @@ -1,4 +1,4 @@ -#standard{width:16;height:undefined("self");border-width:5;variable:11;decrement:9;rounded:11;roundedpx:3px;} +#standard{width:16;height:undefined("self");border-width:5;variable:11;decrement:9;rounded:11;roundedpx:3px;round25:3;round15:2;} #escapes{escaped:-Some::weird(#thing, y);escaped1:-Some::weird(#thing, z);eformat:rgb(32, 128, 64);} #format{format:"rgb(32, 128, 64)";format-string:"hello world";format-multiple:"hello earth 2";format-url-encode:'red is %23ff0000';} #more{width:2px;height:1px;top:50%;} diff --git a/lesscpy/test/less/calls.less b/lesscpy/test/less/calls.less index b420bc7..8cd987a 100644 --- a/lesscpy/test/less/calls.less +++ b/lesscpy/test/less/calls.less @@ -11,6 +11,8 @@ decrement: decrement(@var); rounded: round(@r/3); roundedpx: round(10px / 3); + round25: round(2.5); + round15: round(1.5); } #escapes { escaped: e("-Some::weird(#thing, y)"); From e6512edb98e8b80c6a793986aea75afda406ad1a Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 18:06:10 +0200 Subject: [PATCH 7/8] Add utility away_from_zero_round and use it for LESS round() Also _ophsl should use it when running unter Py3k (instead of convergent_round). --- lesscpy/lessc/color.py | 2 +- lesscpy/lessc/utility.py | 13 +++++++++++++ lesscpy/plib/call.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lesscpy/lessc/color.py b/lesscpy/lessc/color.py index 1a392ed..cde21b3 100644 --- a/lesscpy/lessc/color.py +++ b/lesscpy/lessc/color.py @@ -364,5 +364,5 @@ class Color(): hls = list(self._hextohls(color)) hls[idx] = self._clamp(getattr(hls[idx], op)(diff / 100.0)) rgb = colorsys.hls_to_rgb(*hls) - color = (utility.convergent_round(c * 255) for c in rgb) + color = (utility.away_from_zero_round(c * 255) for c in rgb) return self._rgbatohex(color) diff --git a/lesscpy/lessc/utility.py b/lesscpy/lessc/utility.py index 73f5361..02b1d07 100644 --- a/lesscpy/lessc/utility.py +++ b/lesscpy/lessc/utility.py @@ -13,6 +13,7 @@ from __future__ import print_function import collections import math import re +import sys def flatten(lst): @@ -246,6 +247,18 @@ def split_unit(value): return r.groups() if r else ('', '') +def away_from_zero_round(value, ndigits=0): + """Round half-way away from zero. + + Python2's round() method. + """ + if sys.version_info[0] >= 3: + p = 10 ** ndigits + return float(math.floor((value * p) + math.copysign(0.5, value))) / p + else: + return round(value, ndigits) + + def convergent_round(value, ndigits=0): """Convergent rounding. diff --git a/lesscpy/plib/call.py b/lesscpy/plib/call.py index d2fe8c7..2b8bbcb 100644 --- a/lesscpy/plib/call.py +++ b/lesscpy/plib/call.py @@ -196,7 +196,7 @@ class Call(Node): str """ n, u = utility.analyze_number(value) - return utility.with_unit(int(round(float(n))), u) + return utility.with_unit(int(utility.away_from_zero_round(float(n))), u) def ceil(self, value, *args): """ Ceil number From 92c84c495b2bd04e2e985e3cdfcaa00dff09d11b Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Thu, 8 Aug 2013 18:26:38 +0200 Subject: [PATCH 8/8] Bump version to 0.9j --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index dc0e9ae..c42b00a 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from distutils.core import setup setup( name='lesscpy', - version='0.9i', + version='0.9j', description='Lesscss compiler.', author='Jóhann T Maríusson', author_email='jtm@robot.is',