This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository python-affine.
commit cfa630bdd9984fb7a3556859381200eb4f8d6349 Author: Bas Couwenberg <sebas...@xs4all.nl> Date: Wed Mar 21 07:21:52 2018 +0100 New upstream version 2.2.0 --- .gitignore | 5 +- .travis.yml | 18 +-- CHANGES.txt | 4 + MANIFEST.in | 4 + affine/__init__.py | 20 ++- affine/tests/test_transform.py | 338 ++++++++++++++++++++--------------------- setup.cfg | 5 + setup.py | 16 +- tox.ini | 3 +- 9 files changed, 219 insertions(+), 194 deletions(-) diff --git a/.gitignore b/.gitignore index 51cbe85..44088c5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python env/ +venv/ bin/ build/ develop-eggs/ @@ -31,7 +32,6 @@ htmlcov/ .tox/ .coverage .cache -nosetests.xml coverage.xml # Translations @@ -42,6 +42,9 @@ coverage.xml .project .pydevproject +# PyCharm +.idea/ + # Rope .ropeproject diff --git a/.travis.yml b/.travis.yml index 6951742..6eca268 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,16 @@ sudo: false language: python +cache: pip python: - - "2.7" - - "3.3" - - "3.4" - - "3.5" - - "3.6" + - 2.7 + - 3.6 install: - - "pip install pytest pytest-cov nose" - - "pip install coveralls" - - "pip install -e ." -script: - - python -m pytest affine/tests --cov affine --cov-report term-missing + - pip install wheel --upgrade + - pip install .[test] +script: + - pytest --cov affine --cov-report term-missing after_success: + - pip install coveralls - coveralls deploy: on: diff --git a/CHANGES.txt b/CHANGES.txt index a84acc5..0c5e1a1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,10 @@ CHANGES ======= +2.2.0 (2018-03-20) +------------------ +- Addition of permutation matrix (#35). + 2.1.0 (2017-07-12) ------------------ - Addition of new ``eccentricity`` and ``rotation_angle`` properties (#28). diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bc3503c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +exclude *.rst *.txt *.py +include CHANGES.txt AUTHORS.txt LICENSE.txt VERSION.txt README.rst setup.py setup.cfg +recursive-include affine/tests *.py +exclude MANIFEST.in diff --git a/affine/__init__.py b/affine/__init__.py index b2db1a0..0545624 100644 --- a/affine/__init__.py +++ b/affine/__init__.py @@ -47,7 +47,7 @@ import math __all__ = ['Affine'] __author__ = "Sean Gillies" -__version__ = "2.1.0" +__version__ = "2.2.0" EPSILON = 1e-5 @@ -255,7 +255,20 @@ class Affine( (ca, -sa, px - px * ca + py * sa, sa, ca, py - px * sa - py * ca, 0.0, 0.0, 1.0)) - + + @classmethod + def permutation(cls, *scaling): + """Create the permutation transform. For 2x2 matrices, there is only one permutation matrix that is not the identity. + + :rtype: Affine + """ + + return tuple.__new__( + cls, + (0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0)) + def __str__(self): """Concise string representation.""" return ("|% .2f,% .2f,% .2f|\n" @@ -557,6 +570,3 @@ def dumpsw(obj): """ center = obj * Affine.translation(0.5, 0.5) return '\n'.join(repr(getattr(center, x)) for x in list('adbecf')) + '\n' - - -# vim: ai ts=4 sts=4 et sw=4 tw=78 diff --git a/affine/tests/test_transform.py b/affine/tests/test_transform.py index 2f7c50c..344b551 100644 --- a/affine/tests/test_transform.py +++ b/affine/tests/test_transform.py @@ -30,12 +30,15 @@ from __future__ import division +import os import math import unittest -from textwrap import dedent -from nose.tools import assert_equal, assert_almost_equal, raises +import textwrap -from affine import Affine, EPSILON, UndefinedRotationError +import pytest + +import affine +from affine import Affine, EPSILON def seq_almost_equal(t1, t2, error=0.00001): @@ -46,33 +49,33 @@ def seq_almost_equal(t1, t2, error=0.00001): class PyAffineTestCase(unittest.TestCase): - @raises(TypeError) def test_zero_args(self): - Affine() + with pytest.raises(TypeError): + Affine() - @raises(TypeError) def test_wrong_arg_type(self): - Affine(None) + with pytest.raises(TypeError): + Affine(None) - @raises(TypeError) def test_args_too_few(self): - Affine(1, 2) + with pytest.raises(TypeError): + Affine(1, 2) - @raises(TypeError) def test_args_too_many(self): - Affine(*range(10)) + with pytest.raises(TypeError): + Affine(*range(10)) - @raises(TypeError) def test_args_members_wrong_type(self): - Affine(0, 2, 3, None, None, "") + with pytest.raises(TypeError): + Affine(0, 2, 3, None, None, "") def test_len(self): t = Affine(1, 2, 3, 4, 5, 6) - assert_equal(len(t), 9) + assert len(t) == 9 def test_slice_last_row(self): t = Affine(1, 2, 3, 4, 5, 6) - assert_equal(t[-3:], (0, 0, 1)) + assert t[-3:] == (0, 0, 1) def test_members_are_floats(self): t = Affine(1, 2, 3, 4, 5, 6) @@ -81,67 +84,76 @@ class PyAffineTestCase(unittest.TestCase): def test_getitem(self): t = Affine(1, 2, 3, 4, 5, 6) - assert_equal(t[0], 1) - assert_equal(t[1], 2) - assert_equal(t[2], 3) - assert_equal(t[3], 4) - assert_equal(t[4], 5) - assert_equal(t[5], 6) - assert_equal(t[6], 0) - assert_equal(t[7], 0) - assert_equal(t[8], 1) - assert_equal(t[-1], 1) - - @raises(TypeError) + assert t[0] == 1 + assert t[1] == 2 + assert t[2] == 3 + assert t[3] == 4 + assert t[4] == 5 + assert t[5] == 6 + assert t[6] == 0 + assert t[7] == 0 + assert t[8] == 1 + assert t[-1] == 1 + def test_getitem_wrong_type(self): t = Affine(1, 2, 3, 4, 5, 6) - t['foobar'] + with pytest.raises(TypeError): + t['foobar'] def test_str(self): - assert_equal( - str(Affine(1.111, 2.222, 3.333, -4.444, -5.555, 6.666)), - "| 1.11, 2.22, 3.33|\n|-4.44,-5.55, 6.67|\n| 0.00, 0.00, 1.00|") + assert \ + str(Affine(1.111, 2.222, 3.333, -4.444, -5.555, 6.666)) \ + == "| 1.11, 2.22, 3.33|\n|-4.44,-5.55, 6.67|\n| 0.00, 0.00, 1.00|" def test_repr(self): - assert_equal( - repr(Affine(1.111, 2.222, 3.456, 4.444, 5.5, 6.25)), - ("Affine(1.111, 2.222, 3.456,\n" - " 4.444, 5.5, 6.25)")) + assert \ + repr(Affine(1.111, 2.222, 3.456, 4.444, 5.5, 6.25)) == \ + os.linesep.join(["Affine(1.111, 2.222, 3.456,", " 4.444, 5.5, 6.25)"]) def test_identity_constructor(self): ident = Affine.identity() assert isinstance(ident, Affine) - assert_equal( - tuple(ident), + assert \ + tuple(ident) == \ (1, 0, 0, 0, 1, 0, - 0, 0, 1)) + 0, 0, 1) assert ident.is_identity + def test_permutation_constructor(self): + perm = Affine.permutation() + assert isinstance(perm, Affine) + assert \ + tuple(perm) == \ + (0, 1, 0, + 1, 0, 0, + 0, 0, 1) + assert (perm*perm).is_identity + def test_translation_constructor(self): trans = Affine.translation(2, -5) assert isinstance(trans, Affine) - assert_equal( - tuple(trans), + assert \ + tuple(trans) == \ (1, 0, 2, 0, 1, -5, - 0, 0, 1)) + 0, 0, 1) def test_scale_constructor(self): scale = Affine.scale(5) assert isinstance(scale, Affine) - assert_equal( - tuple(scale), + assert \ + tuple(scale) == \ (5, 0, 0, 0, 5, 0, - 0, 0, 1)) + 0, 0, 1) scale = Affine.scale(-1, 2) - assert_equal( - tuple(scale), + assert \ + tuple(scale) == \ (-1, 0, 0, 0, 2, 0, - 0, 0, 1)) - assert_equal(tuple(Affine.scale(1)), tuple(Affine.identity())) + 0, 0, 1) + assert tuple(Affine.scale(1)) == tuple(Affine.identity()) def test_shear_constructor(self): shear = Affine.shear(30) @@ -172,11 +184,11 @@ class PyAffineTestCase(unittest.TestCase): assert isinstance(rot, Affine) r = math.radians(60) s, c = math.sin(r), math.cos(r) - assert_equal( - tuple(rot), + assert \ + tuple(rot) == \ (c, -s, 0, s, c, 0, - 0, 0, 1)) + 0, 0, 1) rot = Affine.rotation(337) r = math.radians(337) s, c = math.sin(r), math.cos(r) @@ -185,82 +197,80 @@ class PyAffineTestCase(unittest.TestCase): (c, -s, 0, s, c, 0, 0, 0, 1)) - assert_equal(tuple(Affine.rotation(0)), tuple(Affine.identity())) + assert tuple(Affine.rotation(0)) == tuple(Affine.identity()) def test_rotation_constructor_quadrants(self): - assert_equal( - tuple(Affine.rotation(0)), + assert \ + tuple(Affine.rotation(0)) == \ (1, 0, 0, 0, 1, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(90)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(90)) == \ (0, -1, 0, 1, 0, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(180)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(180)) == \ (-1, 0, 0, 0, -1, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(-180)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(-180)) == \ (-1, 0, 0, 0, -1, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(270)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(270)) == \ (0, 1, 0, -1, 0, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(-90)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(-90)) == \ (0, 1, 0, -1, 0, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(360)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(360)) == \ (1, 0, 0, 0, 1, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(450)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(450)) == \ (0, -1, 0, 1, 0, 0, - 0, 0, 1)) - assert_equal( - tuple(Affine.rotation(-450)), + 0, 0, 1) + assert \ + tuple(Affine.rotation(-450)) == \ (0, 1, 0, -1, 0, 0, - 0, 0, 1)) + 0, 0, 1) def test_rotation_constructor_with_pivot(self): - assert_equal(tuple(Affine.rotation(60)), - tuple(Affine.rotation(60, pivot=(0, 0)))) + assert tuple(Affine.rotation(60)) == tuple(Affine.rotation(60, pivot=(0, 0))) rot = Affine.rotation(27, pivot=(2, -4)) r = math.radians(27) s, c = math.sin(r), math.cos(r) - assert_equal( - tuple(rot), + assert \ + tuple(rot) == \ (c, -s, 2 - 2 * c - 4 * s, s, c, -4 - 2 * s + 4 * c, - 0, 0, 1)) - assert_equal(tuple(Affine.rotation(0, (-3, 2))), - tuple(Affine.identity())) + 0, 0, 1) + assert tuple(Affine.rotation(0, (-3, 2))) == tuple(Affine.identity()) - @raises(TypeError) def test_rotation_contructor_wrong_arg_types(self): - Affine.rotation(1, 1) + with pytest.raises(TypeError): + Affine.rotation(1, 1) def test_determinant(self): - assert_equal(Affine.identity().determinant, 1) - assert_equal(Affine.scale(2).determinant, 4) - assert_equal(Affine.scale(0).determinant, 0) - assert_equal(Affine.scale(5, 1).determinant, 5) - assert_equal(Affine.scale(-1, 1).determinant, -1) - assert_equal(Affine.scale(-1, 0).determinant, 0) - assert_almost_equal(Affine.rotation(77).determinant, 1) - assert_almost_equal(Affine.translation(32, -47).determinant, 1) + assert Affine.identity().determinant == 1 + assert Affine.scale(2).determinant == 4 + assert Affine.scale(0).determinant == 0 + assert Affine.scale(5, 1).determinant == 5 + assert Affine.scale(-1, 1).determinant == -1 + assert Affine.scale(-1, 0).determinant == 0 + assert Affine.rotation(77).determinant == pytest.approx(1) + assert Affine.translation(32, -47).determinant == pytest.approx(1) def test_is_rectilinear(self): assert Affine.identity().is_rectilinear @@ -304,9 +314,9 @@ class PyAffineTestCase(unittest.TestCase): assert isinstance(a, tuple) assert isinstance(b, tuple) assert isinstance(c, tuple) - assert_equal(a, (2, 5)) - assert_equal(b, (3, 6)) - assert_equal(c, (4, 7)) + assert a == (2, 5) + assert b == (3, 6) + assert c == (4, 7) def test_almost_equals(self): EPSILON = 1e-5 @@ -345,25 +355,25 @@ class PyAffineTestCase(unittest.TestCase): assert not t1 == 1 assert t1 != 1 - @raises(TypeError) def test_gt(self): - Affine(1, 2, 3, 4, 5, 6) > Affine(6, 5, 4, 3, 2, 1) + with pytest.raises(TypeError): + Affine(1, 2, 3, 4, 5, 6) > Affine(6, 5, 4, 3, 2, 1) - @raises(TypeError) def test_lt(self): - Affine(1, 2, 3, 4, 5, 6) < Affine(6, 5, 4, 3, 2, 1) + with pytest.raises(TypeError): + Affine(1, 2, 3, 4, 5, 6) < Affine(6, 5, 4, 3, 2, 1) - @raises(TypeError) def test_add(self): - Affine(1, 2, 3, 4, 5, 6) + Affine(6, 5, 4, 3, 2, 1) + with pytest.raises(TypeError): + Affine(1, 2, 3, 4, 5, 6) + Affine(6, 5, 4, 3, 2, 1) - @raises(TypeError) def test_sub(self): - Affine(1, 2, 3, 4, 5, 6) - Affine(6, 5, 4, 3, 2, 1) + with pytest.raises(TypeError): + Affine(1, 2, 3, 4, 5, 6) - Affine(6, 5, 4, 3, 2, 1) def test_mul_by_identity(self): t = Affine(1, 2, 3, 4, 5, 6) - assert_equal(tuple(t * Affine.identity()), tuple(t)) + assert tuple(t * Affine.identity()) == tuple(t) def test_mul_transform(self): t = Affine.rotation(5) * Affine.rotation(29) @@ -376,13 +386,12 @@ class PyAffineTestCase(unittest.TestCase): pts = [(4, 1), (-1, 0), (3, 2)] r = Affine.scale(-2).itransform(pts) assert r is None, r - assert_equal(pts, [(-8, -2), (2, 0), (-6, -4)]) + assert pts == [(-8, -2), (2, 0), (-6, -4)] - @raises(TypeError) def test_mul_wrong_type(self): - Affine(1, 2, 3, 4, 5, 6) * None + with pytest.raises(TypeError): + Affine(1, 2, 3, 4, 5, 6) * None - @raises(TypeError) def test_mul_sequence_wrong_member_types(self): class NotPtSeq: @classmethod @@ -392,7 +401,8 @@ class PyAffineTestCase(unittest.TestCase): def __iter__(self): yield 0 - Affine(1, 2, 3, 4, 5, 6) * NotPtSeq() + with pytest.raises(TypeError): + Affine(1, 2, 3, 4, 5, 6) * NotPtSeq() def test_imul_transform(self): t = Affine.translation(3, 5) @@ -410,51 +420,47 @@ class PyAffineTestCase(unittest.TestCase): seq_almost_equal(~t * t, Affine.identity()) def test_cant_invert_degenerate(self): - from affine import TransformNotInvertibleError t = Affine.scale(0) - self.assertRaises(TransformNotInvertibleError, lambda: ~t) + with pytest.raises(affine.TransformNotInvertibleError): + ~t - @raises(TypeError) def test_bad_type_world(self): - from affine import loadsw - # wrong type, i.e don't use readlines() - loadsw(['1.0', '0.0', '0.0', '1.0', '0.0', '0.0']) + """wrong type, i.e don't use readlines()""" + with pytest.raises(TypeError): + affine.loadsw(['1.0', '0.0', '0.0', '1.0', '0.0', '0.0']) - @raises(ValueError) def test_bad_value_world(self): - from affine import loadsw - # wrong number of parameters - loadsw('1.0\n0.0\n0.0\n1.0\n0.0\n0.0\n0.0') + """Wrong number of parameters.""" + with pytest.raises(ValueError): + affine.loadsw('1.0\n0.0\n0.0\n1.0\n0.0\n0.0\n0.0') def test_simple_world(self): - from affine import loadsw, dumpsw s = '1.0\n0.0\n0.0\n-1.0\n100.5\n199.5\n' - a = loadsw(s) - self.assertEqual( - a, + a = affine.loadsw(s) + assert \ + a == \ Affine( 1.0, 0.0, 100.0, - 0.0, -1., 200.0)) - self.assertEqual(dumpsw(a), s) + 0.0, -1., 200.0) + assert affine.dumpsw(a) == s def test_real_world(self): - from affine import loadsw, dumpsw - s = dedent('''\ + s = textwrap.dedent('''\ 39.9317755024 30.0907511581 30.0907511576 -39.9317755019 2658137.2266720217 5990821.7039887439''') # no EOL - a1 = loadsw(s) - self.assertTrue(a1.almost_equals( + a1 = affine.loadsw(s) + assert a1.almost_equals( Affine( 39.931775502364644, 30.090751157602412, 2658102.2154086917, - 30.090751157602412, -39.931775502364644, 5990826.624500916))) - a1out = dumpsw(a1) - self.assertTrue(isinstance(a1out, str)) - a2 = loadsw(a1out) - self.assertTrue(a1.almost_equals(a2)) + 30.090751157602412, -39.931775502364644, 5990826.624500916)) + a1out = affine.dumpsw(a1) + assert isinstance(a1out, str) + a2 = affine.loadsw(a1out) + assert a1.almost_equals(a2) # We're using pytest for tests added after 1.0 and don't need unittest @@ -518,48 +524,42 @@ def test_roundtrip(): def test_eccentricity(): - assert_equal(Affine.identity().eccentricity, 0.0) - assert_equal(Affine.scale(2).eccentricity, 0.0) + assert Affine.identity().eccentricity == 0.0 + assert Affine.scale(2).eccentricity == 0.0 #assert_equal(Affine.scale(0).eccentricity, ?) - assert_almost_equal(Affine.scale(2, 1).eccentricity, math.sqrt(3) / 2) - assert_almost_equal(Affine.scale(2, 3).eccentricity, math.sqrt(5) / 3) - assert_equal(Affine.scale(1, 0).eccentricity, 1.0) - assert_almost_equal(Affine.rotation(77).eccentricity, 0.0) - assert_almost_equal(Affine.translation(32, -47).eccentricity, 0.0) - assert_almost_equal(Affine.scale(-1, 1).eccentricity, 0.0) + assert Affine.scale(2, 1).eccentricity == pytest.approx(math.sqrt(3) / 2) + assert Affine.scale(2, 3).eccentricity == pytest.approx(math.sqrt(5) / 3) + assert Affine.scale(1, 0).eccentricity == 1.0 + assert Affine.rotation(77).eccentricity == pytest.approx(0.0) + assert Affine.translation(32, -47).eccentricity == pytest.approx(0.0) + assert Affine.scale(-1, 1).eccentricity == pytest.approx(0.0) def test_eccentricity_complex(): - assert_almost_equal( - (Affine.scale(2, 3) * Affine.rotation(77)).eccentricity, - math.sqrt(5) / 3 - ) - assert_almost_equal( - (Affine.rotation(77) * Affine.scale(2, 3)).eccentricity, - math.sqrt(5) / 3 - ) - assert_almost_equal( - (Affine.translation(32, -47) * Affine.rotation(77) * Affine.scale(2, 3)).eccentricity, - math.sqrt(5) / 3 - ) + assert \ + (Affine.scale(2, 3) * Affine.rotation(77)).eccentricity == \ + pytest.approx(math.sqrt(5) / 3) + assert \ + (Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \ + pytest.approx(math.sqrt(5) / 3) + assert \ + (Affine.translation(32, -47) * Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \ + pytest.approx(math.sqrt(5) / 3) def test_rotation_angle(): - assert_equal(Affine.identity().rotation_angle, 0.0) - assert_equal(Affine.scale(2).rotation_angle, 0.0) - assert_equal(Affine.scale(2, 1).rotation_angle, 0.0) - assert_almost_equal(Affine.translation(32, -47).rotation_angle, 0.0) - assert_almost_equal(Affine.rotation(30).rotation_angle, 30) - assert_almost_equal(Affine.rotation(-150).rotation_angle, -150) + assert Affine.identity().rotation_angle == 0.0 + assert Affine.scale(2).rotation_angle == 0.0 + assert Affine.scale(2, 1).rotation_angle == 0.0 + assert Affine.translation(32, -47).rotation_angle == pytest.approx(0.0) + assert Affine.rotation(30).rotation_angle == pytest.approx(30) + assert Affine.rotation(-150).rotation_angle == pytest.approx(-150) -@raises(UndefinedRotationError) def test_rotation_improper(): - Affine.scale(-1, 1).rotation_angle + with pytest.raises(affine.UndefinedRotationError): + Affine.scale(-1, 1).rotation_angle if __name__ == '__main__': unittest.main() - - -# vim: ai ts=4 sts=4 et sw=4 tw=78 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a98ce0d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal: 1 + +[tool:pytest] +testpaths: affine/tests diff --git a/setup.py b/setup.py index cc704b1..abf399c 100755 --- a/setup.py +++ b/setup.py @@ -1,22 +1,24 @@ -from codecs import open as codecs_open -from setuptools import setup, find_packages +import codecs +import os + +from setuptools import find_packages, setup # Parse the version from the affine module. -with open('affine/__init__.py') as f: +with codecs.open(os.path.join('affine', '__init__.py')) as f: for line in f: if "__version__" in line: version = line.split("=")[1].strip() version = version.strip('"').strip("'") break -with codecs_open('README.rst', encoding='utf-8') as f: +with codecs.open('README.rst', encoding='utf-8') as f: readme = f.read() setup(name='affine', version=version, - description="Matrices describing affine transformation of the plane", + description="Matrices describing affine transformation of the plane.", long_description=readme, classifiers=[], keywords='affine transformation matrix', @@ -26,6 +28,6 @@ setup(name='affine', license='BSD', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, - zip_safe=False, - extras_require = {'test': ['pytest']} + zip_safe=True, + extras_require={'test': ['pytest>=3.0', 'pytest-cov']} ) diff --git a/tox.ini b/tox.ini index 407da79..be6691f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ [tox] envlist = - py27,py34,py35 + py27,py36 [testenv] usedevelop = true deps = - nose pytest-cov responses commands = -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-affine.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel