[Scipy-svn] r6862 - in trunk/scipy/io/matlab: . tests

scipy-svn@scip... scipy-svn@scip...
Thu Nov 11 18:56:10 CST 2010


Author: matthew.brett@gmail.com
Date: 2010-11-11 18:56:10 -0600 (Thu, 11 Nov 2010)
New Revision: 6862

Modified:
   trunk/scipy/io/matlab/mio5.py
   trunk/scipy/io/matlab/mio5_params.py
   trunk/scipy/io/matlab/mio5_utils.pyx
   trunk/scipy/io/matlab/tests/test_mio5_utils.py
Log:
RF: move dtype etc calculation to readers / writers

Modified: trunk/scipy/io/matlab/mio5.py
===================================================================
--- trunk/scipy/io/matlab/mio5.py	2010-11-12 00:56:02 UTC (rev 6861)
+++ trunk/scipy/io/matlab/mio5.py	2010-11-12 00:56:10 UTC (rev 6862)
@@ -86,8 +86,10 @@
 
 import scipy.sparse
 
+import byteordercodes as boc
+
 from miobase import MatFileReader, docfiller, matdims, \
-     read_dtype, convert_dtypes, arr_to_chars, arr_dtype_number, \
+     read_dtype, arr_to_chars, arr_dtype_number, \
      MatWriteError, MatReadError
 
 # Reader object for matlab 5 format variables
@@ -95,139 +97,12 @@
 
 # Constants and helper objects
 from mio5_params import MatlabObject, MatlabFunction, \
-    miINT8, miUINT8, miINT16, miUINT16, miINT32, miUINT32, \
-    miSINGLE, miDOUBLE, miINT64, miUINT64, miMATRIX, \
-    miCOMPRESSED, miUTF8, miUTF16, miUTF32, \
-    mxCELL_CLASS, mxSTRUCT_CLASS, mxOBJECT_CLASS, mxCHAR_CLASS, \
-    mxSPARSE_CLASS, mxDOUBLE_CLASS, mxSINGLE_CLASS, mxINT8_CLASS, \
-    mxUINT8_CLASS, mxINT16_CLASS, mxUINT16_CLASS, mxINT32_CLASS, \
-    mxUINT32_CLASS, mxINT64_CLASS, mxUINT64_CLASS
+        MDTYPES, NP_TO_MTYPES, NP_TO_MXTYPES, \
+        miCOMPRESSED, miMATRIX, miINT8, miUTF8, miUINT32, \
+        mxCELL_CLASS, mxSTRUCT_CLASS, mxOBJECT_CLASS, mxCHAR_CLASS, \
+        mxSPARSE_CLASS, mxDOUBLE_CLASS
 
 
-mdtypes_template = {
-    miINT8: 'i1',
-    miUINT8: 'u1',
-    miINT16: 'i2',
-    miUINT16: 'u2',
-    miINT32: 'i4',
-    miUINT32: 'u4',
-    miSINGLE: 'f4',
-    miDOUBLE: 'f8',
-    miINT64: 'i8',
-    miUINT64: 'u8',
-    miUTF8: 'u1',
-    miUTF16: 'u2',
-    miUTF32: 'u4',
-    'file_header': [('description', 'S116'),
-                    ('subsystem_offset', 'i8'),
-                    ('version', 'u2'),
-                    ('endian_test', 'S2')],
-    'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
-    'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
-    'array_flags': [('data_type', 'u4'),
-                    ('byte_count', 'u4'),
-                    ('flags_class','u4'),
-                    ('nzmax', 'u4')],
-    'U1': 'U1',
-    }
-
-mclass_dtypes_template = {
-    mxINT8_CLASS: 'i1',
-    mxUINT8_CLASS: 'u1',
-    mxINT16_CLASS: 'i2',
-    mxUINT16_CLASS: 'u2',
-    mxINT32_CLASS: 'i4',
-    mxUINT32_CLASS: 'u4',
-    mxINT64_CLASS: 'i8',
-    mxUINT64_CLASS: 'u8',
-    mxSINGLE_CLASS: 'f4',
-    mxDOUBLE_CLASS: 'f8',
-    }
-
-
-np_to_mtypes = {
-    'f8': miDOUBLE,
-    'c32': miDOUBLE,
-    'c24': miDOUBLE,
-    'c16': miDOUBLE,
-    'f4': miSINGLE,
-    'c8': miSINGLE,
-    'i1': miINT8,
-    'i2': miINT16,
-    'i4': miINT32,
-    'i8': miINT64,
-    'u1': miUINT8,
-    'u2': miUINT16,
-    'u4': miUINT32,
-    'u8': miUINT64,
-    'S1': miUINT8,
-    'U1': miUTF16,
-    }
-
-
-np_to_mxtypes = {
-    'f8': mxDOUBLE_CLASS,
-    'c32': mxDOUBLE_CLASS,
-    'c24': mxDOUBLE_CLASS,
-    'c16': mxDOUBLE_CLASS,
-    'f4': mxSINGLE_CLASS,
-    'c8': mxSINGLE_CLASS,
-    'i8': mxINT64_CLASS,
-    'i4': mxINT32_CLASS,
-    'i2': mxINT16_CLASS,
-    'u8': mxUINT64_CLASS,
-    'u2': mxUINT16_CLASS,
-    'u1': mxUINT8_CLASS,
-    'S1': mxUINT8_CLASS,
-    }
-
-
-
-''' Before release v7.1 (release 14) matlab (TM) used the system
-default character encoding scheme padded out to 16-bits. Release 14
-and later use Unicode. When saving character data, R14 checks if it
-can be encoded in 7-bit ascii, and saves in that format if so.'''
-
-codecs_template = {
-    miUTF8: {'codec': 'utf_8', 'width': 1},
-    miUTF16: {'codec': 'utf_16', 'width': 2},
-    miUTF32: {'codec': 'utf_32','width': 4},
-    }
-
-
-def convert_codecs(template, byte_order):
-    ''' Convert codec template mapping to byte order
-
-    Set codecs not on this system to None
-
-    Parameters
-    ----------
-    template : mapping
-       key, value are respectively codec name, and root name for codec
-       (without byte order suffix)
-    byte_order : {'<', '>'}
-       code for little or big endian
-
-    Returns
-    -------
-    codecs : dict
-       key, value are name, codec (as in .encode(codec))
-    '''
-    codecs = {}
-    postfix = byte_order == '<' and '_le' or '_be'
-    for k, v in template.items():
-        codec = v['codec']
-        try:
-            " ".encode(codec)
-        except LookupError:
-            codecs[k] = None
-            continue
-        if v['width'] > 1:
-            codec += postfix
-        codecs[k] = codec
-    return codecs.copy()
-
-
 class MatFile5Reader(MatFileReader):
     ''' Reader for Mat 5 mat files
     Adds the following attribute to base class
@@ -281,10 +156,6 @@
         if not uint16_codec:
             uint16_codec = sys.getdefaultencoding()
         self.uint16_codec = uint16_codec
-        # placeholders for dtypes, codecs - see initialize_read
-        self.dtypes = None
-        self.class_dtypes = None
-        self.codecs = None
         # placeholders for readers - see initialize_read method
         self._file_reader = None
         self._matrix_reader = None
@@ -300,7 +171,8 @@
     def read_file_header(self):
         ''' Read in mat 5 file header '''
         hdict = {}
-        hdr = read_dtype(self.mat_stream, self.dtypes['file_header'])
+        hdr_dtype = MDTYPES[self.byte_order]['dtypes']['file_header']
+        hdr = read_dtype(self.mat_stream, hdr_dtype)
         hdict['__header__'] = hdr['description'].item().strip(asbytes(' \t\n\000'))
         v_major = hdr['version'] >> 8
         v_minor = hdr['version'] & 0xFF
@@ -312,15 +184,6 @@
 
         Sets up readers from parameters in `self`
         '''
-        self.dtypes = convert_dtypes(mdtypes_template, self.byte_order)
-        self.class_dtypes = convert_dtypes(mclass_dtypes_template,
-                                           self.byte_order)
-        self.codecs = convert_codecs(codecs_template, self.byte_order)
-        uint16_codec = self.uint16_codec
-        # Set length of miUINT16 char encoding
-        self.codecs['uint16_len'] = len("  ".encode(uint16_codec)) \
-                               - len(" ".encode(uint16_codec))
-        self.codecs['uint16_codec'] = uint16_codec
         # reader for top level stream.  We need this extra top-level
         # reader because we use the matrix_reader object to contain
         # compressed matrices (so they have their own stream)
@@ -481,7 +344,7 @@
     rdr = MatFile5Reader(file_obj)
     file_obj.seek(0)
     # Raw read of top-level file header
-    hdr_len = np.dtype(mdtypes_template['file_header']).itemsize
+    hdr_len = MDTYPES[boc.native_code]['dtypes']['file_header'].itemsize
     raw_hdr = file_obj.read(hdr_len)
     # Initialize variable reading
     file_obj.seek(0)
@@ -594,9 +457,16 @@
     return narr
 
 
+# Native byte ordered dtypes for convenience for writers
+NDT_FILE_HDR = MDTYPES[boc.native_code]['dtypes']['file_header']
+NDT_TAG_FULL = MDTYPES[boc.native_code]['dtypes']['tag_full']
+NDT_TAG_SMALL = MDTYPES[boc.native_code]['dtypes']['tag_smalldata']
+NDT_ARRAY_FLAGS = MDTYPES[boc.native_code]['dtypes']['array_flags']
+
+
 class VarWriter5(object):
     ''' Generic matlab matrix writing class '''
-    mat_tag = np.zeros((), mdtypes_template['tag_full'])
+    mat_tag = np.zeros((), NDT_TAG_FULL)
     mat_tag['mdtype'] = miMATRIX
 
     def __init__(self, file_writer):
@@ -617,7 +487,7 @@
     def write_element(self, arr, mdtype=None):
         ''' write tag and data '''
         if mdtype is None:
-            mdtype = np_to_mtypes[arr.dtype.str[1:]]
+            mdtype = NP_TO_MTYPES[arr.dtype.str[1:]]
         byte_count = arr.size*arr.itemsize
         if byte_count <= 4:
             self.write_smalldata_element(arr, mdtype, byte_count)
@@ -626,7 +496,7 @@
 
     def write_smalldata_element(self, arr, mdtype, byte_count):
         # write tag with embedded data
-        tag = np.zeros((), mdtypes_template['tag_smalldata'])
+        tag = np.zeros((), NDT_TAG_SMALL)
         tag['byte_count_mdtype'] = (byte_count << 16) + mdtype
         # if arr.tostring is < 4, the element will be zero-padded as needed.
         tag['data'] = arr.tostring(order='F')
@@ -634,7 +504,7 @@
 
     def write_regular_element(self, arr, mdtype, byte_count):
         # write tag, data
-        tag = np.zeros((), mdtypes_template['tag_full'])
+        tag = np.zeros((), NDT_TAG_FULL)
         tag['mdtype'] = mdtype
         tag['byte_count'] = byte_count
         self.write_bytes(tag)
@@ -668,7 +538,7 @@
         self._mat_tag_pos = self.file_stream.tell()
         self.write_bytes(self.mat_tag)
         # write array flags (complex, global, logical, class, nzmax)
-        af = np.zeros((), mdtypes_template['array_flags'])
+        af = np.zeros((), NDT_ARRAY_FLAGS)
         af['data_type'] = miUINT32
         af['byte_count'] = 8
         flags = is_complex << 3 | is_global << 2 | is_logical << 1
@@ -755,7 +625,7 @@
     def write_numeric(self, arr):
         imagf = arr.dtype.kind == 'c'
         try:
-            mclass = np_to_mxtypes[arr.dtype.str[1:]]
+            mclass = NP_TO_MXTYPES[arr.dtype.str[1:]]
         except KeyError:
             if imagf:
                 arr = arr.astype('c128')
@@ -875,6 +745,7 @@
 
 class MatFile5Writer(object):
     ''' Class for writing mat5 files '''
+
     @docfiller
     def __init__(self, file_stream,
                  do_compression=False,
@@ -912,7 +783,7 @@
 
     def write_file_header(self):
         # write header
-        hdr =  np.zeros((), mdtypes_template['file_header'])
+        hdr =  np.zeros((), NDT_FILE_HDR)
         hdr['description']='MATLAB 5.0 MAT-file Platform: %s, Created on: %s' \
             % (os.name,time.asctime())
         hdr['version']= 0x0100
@@ -953,7 +824,7 @@
                 self._matrix_writer.file_stream = stream
                 self._matrix_writer.write_top(var, asbytes(name), is_global)
                 out_str = zlib.compress(stream.getvalue())
-                tag = np.empty((), mdtypes_template['tag_full'])
+                tag = np.empty((), NDT_TAG_FULL)
                 tag['mdtype'] = miCOMPRESSED
                 tag['byte_count'] = len(out_str)
                 self.file_stream.write(tag.tostring() + out_str)

Modified: trunk/scipy/io/matlab/mio5_params.py
===================================================================
--- trunk/scipy/io/matlab/mio5_params.py	2010-11-12 00:56:02 UTC (rev 6861)
+++ trunk/scipy/io/matlab/mio5_params.py	2010-11-12 00:56:10 UTC (rev 6862)
@@ -7,6 +7,7 @@
 
 import numpy as np
 
+from miobase import convert_dtypes
 
 miINT8 = 1
 miUINT8 = 2
@@ -51,7 +52,137 @@
 # https://www-old.cae.wisc.edu/pipermail/octave-maintainers/2007-May/002824.html
 mxOBJECT_CLASS_FROM_MATRIX_H = 18
 
+mdtypes_template = {
+    miINT8: 'i1',
+    miUINT8: 'u1',
+    miINT16: 'i2',
+    miUINT16: 'u2',
+    miINT32: 'i4',
+    miUINT32: 'u4',
+    miSINGLE: 'f4',
+    miDOUBLE: 'f8',
+    miINT64: 'i8',
+    miUINT64: 'u8',
+    miUTF8: 'u1',
+    miUTF16: 'u2',
+    miUTF32: 'u4',
+    'file_header': [('description', 'S116'),
+                    ('subsystem_offset', 'i8'),
+                    ('version', 'u2'),
+                    ('endian_test', 'S2')],
+    'tag_full': [('mdtype', 'u4'), ('byte_count', 'u4')],
+    'tag_smalldata':[('byte_count_mdtype', 'u4'), ('data', 'S4')],
+    'array_flags': [('data_type', 'u4'),
+                    ('byte_count', 'u4'),
+                    ('flags_class','u4'),
+                    ('nzmax', 'u4')],
+    'U1': 'U1',
+    }
 
+mclass_dtypes_template = {
+    mxINT8_CLASS: 'i1',
+    mxUINT8_CLASS: 'u1',
+    mxINT16_CLASS: 'i2',
+    mxUINT16_CLASS: 'u2',
+    mxINT32_CLASS: 'i4',
+    mxUINT32_CLASS: 'u4',
+    mxINT64_CLASS: 'i8',
+    mxUINT64_CLASS: 'u8',
+    mxSINGLE_CLASS: 'f4',
+    mxDOUBLE_CLASS: 'f8',
+    }
+
+
+NP_TO_MTYPES = {
+    'f8': miDOUBLE,
+    'c32': miDOUBLE,
+    'c24': miDOUBLE,
+    'c16': miDOUBLE,
+    'f4': miSINGLE,
+    'c8': miSINGLE,
+    'i1': miINT8,
+    'i2': miINT16,
+    'i4': miINT32,
+    'i8': miINT64,
+    'u1': miUINT8,
+    'u2': miUINT16,
+    'u4': miUINT32,
+    'u8': miUINT64,
+    'S1': miUINT8,
+    'U1': miUTF16,
+    }
+
+
+NP_TO_MXTYPES = {
+    'f8': mxDOUBLE_CLASS,
+    'c32': mxDOUBLE_CLASS,
+    'c24': mxDOUBLE_CLASS,
+    'c16': mxDOUBLE_CLASS,
+    'f4': mxSINGLE_CLASS,
+    'c8': mxSINGLE_CLASS,
+    'i8': mxINT64_CLASS,
+    'i4': mxINT32_CLASS,
+    'i2': mxINT16_CLASS,
+    'u8': mxUINT64_CLASS,
+    'u2': mxUINT16_CLASS,
+    'u1': mxUINT8_CLASS,
+    'S1': mxUINT8_CLASS,
+    }
+
+''' Before release v7.1 (release 14) matlab (TM) used the system
+default character encoding scheme padded out to 16-bits. Release 14
+and later use Unicode. When saving character data, R14 checks if it
+can be encoded in 7-bit ascii, and saves in that format if so.'''
+
+codecs_template = {
+    miUTF8: {'codec': 'utf_8', 'width': 1},
+    miUTF16: {'codec': 'utf_16', 'width': 2},
+    miUTF32: {'codec': 'utf_32','width': 4},
+    }
+
+
+def _convert_codecs(template, byte_order):
+    ''' Convert codec template mapping to byte order
+
+    Set codecs not on this system to None
+
+    Parameters
+    ----------
+    template : mapping
+       key, value are respectively codec name, and root name for codec
+       (without byte order suffix)
+    byte_order : {'<', '>'}
+       code for little or big endian
+
+    Returns
+    -------
+    codecs : dict
+       key, value are name, codec (as in .encode(codec))
+    '''
+    codecs = {}
+    postfix = byte_order == '<' and '_le' or '_be'
+    for k, v in template.items():
+        codec = v['codec']
+        try:
+            " ".encode(codec)
+        except LookupError:
+            codecs[k] = None
+            continue
+        if v['width'] > 1:
+            codec += postfix
+        codecs[k] = codec
+    return codecs.copy()
+
+
+MDTYPES = {}
+for _bytecode in '<>':
+    _def = {}
+    _def['dtypes'] = convert_dtypes(mdtypes_template, _bytecode)
+    _def['classes'] = convert_dtypes(mclass_dtypes_template, _bytecode)
+    _def['codecs'] = _convert_codecs(codecs_template, _bytecode)
+    MDTYPES[_bytecode] = _def
+
+
 class mat_struct(object):
     ''' Placeholder for holding read data from structs
 

Modified: trunk/scipy/io/matlab/mio5_utils.pyx
===================================================================
--- trunk/scipy/io/matlab/mio5_utils.pyx	2010-11-12 00:56:02 UTC (rev 6861)
+++ trunk/scipy/io/matlab/mio5_utils.pyx	2010-11-12 00:56:10 UTC (rev 6862)
@@ -135,8 +135,6 @@
     cdef PyObject* dtypes[_N_MIS]
     # pointers to stuff in preader.class_dtypes
     cdef PyObject* class_dtypes[_N_MXS]
-    # necessary to keep memory alive for .dtypes, .class_dtypes
-    cdef object preader
     # cached here for convenience in later array creation
     cdef cnp.dtype U1_dtype
     cdef cnp.dtype bool_dtype
@@ -145,9 +143,22 @@
         int mat_dtype
         int squeeze_me
         int chars_as_strings
-        
+
+    """ Initialize from file reader object
+
+    preader needs the following fields defined:
+
+    * mat_stream (file-like)
+    * byte_order (str)
+    * uint16_codec (str)
+    * struct_as_record (bool)
+    * chars_as_strings (bool)
+    * mat_dtype (bool)
+    * squeeze_me (bool)
+    """
     def __new__(self, preader):
-        self.is_swapped = preader.byte_order == swapped_code
+        byte_order = preader.byte_order
+        self.is_swapped = byte_order == swapped_code
         if self.is_swapped:
             self.little_endian = not sys_is_le
         else:
@@ -155,24 +166,27 @@
         # option affecting reading of matlab struct arrays
         self.struct_as_record = preader.struct_as_record
         # store codecs for text matrix reading
-        self.codecs = preader.codecs
+        self.codecs = mio5p.MDTYPES[byte_order]['codecs'].copy()
         self.uint16_codec = preader.uint16_codec
+        uint16_codec = self.uint16_codec
+        # Set length of miUINT16 char encoding
+        self.codecs['uint16_len'] = len("  ".encode(uint16_codec)) \
+                - len(" ".encode(uint16_codec))
+        self.codecs['uint16_codec'] = uint16_codec
         # set c-optimized stream object from python file-like object
         self.set_stream(preader.mat_stream)
         # options for element processing
         self.mat_dtype = preader.mat_dtype
         self.chars_as_strings = preader.chars_as_strings
         self.squeeze_me = preader.squeeze_me
-        # copy refs to dtypes into object pointer array. Store preader
-        # to keep preader.dtypes, class_dtypes alive. We only need the
+        # copy refs to dtypes into object pointer array. We only need the
         # integer-keyed dtypes
-        self.preader = preader
-        for key, dt in preader.dtypes.items():
+        for key, dt in mio5p.MDTYPES[byte_order]['dtypes'].items():
             if isinstance(key, str):
                 continue
             self.dtypes[key] = <PyObject*>dt
         # copy refs to class_dtypes into object pointer array
-        for key, dt in preader.class_dtypes.items():
+        for key, dt in mio5p.MDTYPES[byte_order]['classes'].items():
             if isinstance(key, str):
                 continue
             self.class_dtypes[key] = <PyObject*>dt

Modified: trunk/scipy/io/matlab/tests/test_mio5_utils.py
===================================================================
--- trunk/scipy/io/matlab/tests/test_mio5_utils.py	2010-11-12 00:56:02 UTC (rev 6861)
+++ trunk/scipy/io/matlab/tests/test_mio5_utils.py	2010-11-12 00:56:10 UTC (rev 6862)
@@ -20,8 +20,7 @@
 
 import scipy.io.matlab.byteordercodes as boc
 import scipy.io.matlab.streams as streams
-import scipy.io.matlab.miobase as miob
-import scipy.io.matlab.mio5 as mio5
+import scipy.io.matlab.mio5_params as mio5p
 import scipy.io.matlab.mio5_utils as m5u
 
 
@@ -84,11 +83,8 @@
         pass
     r = R()
     r.byte_order = boc.native_code
-    r.dtypes = {}
-    r.class_dtypes = {}
-    r.codecs = {}
     r.struct_as_record = True
-    r.uint16_codec = None
+    r.uint16_codec = sys.getdefaultencoding()
     r.chars_as_strings = False
     r.mat_dtype = False
     r.squeeze_me = False
@@ -105,14 +101,14 @@
     # This works for StringIO but _not_ cStringIO
     yield assert_raises, IOError, c_reader.read_tag
     # bad SDE
-    tag = _make_tag('i4', 1, mio5.miINT32, sde=True)
+    tag = _make_tag('i4', 1, mio5p.miINT32, sde=True)
     tag['byte_count'] = 5
     _write_stream(str_io, tag.tostring())
     yield assert_raises, ValueError, c_reader.read_tag
 
 
 def test_read_stream():
-    tag = _make_tag('i4', 1, mio5.miINT32, sde=True)
+    tag = _make_tag('i4', 1, mio5p.miINT32, sde=True)
     tag_str = tag.tostring()
     str_io = cStringIO(tag_str)
     st = streams.make_stream(str_io)
@@ -127,12 +123,12 @@
     r.mat_stream = str_io
     # check simplest of tags
     for base_dt, val, mdtype in (
-        ('u2', 30, mio5.miUINT16),
-        ('i4', 1, mio5.miINT32),
-        ('i2', -1, mio5.miINT16)):
+        ('u2', 30, mio5p.miUINT16),
+        ('i4', 1, mio5p.miINT32),
+        ('i2', -1, mio5p.miINT16)):
         for byte_code in ('<', '>'):
             r.byte_order = byte_code
-            r.dtypes = miob.convert_dtypes(mio5.mdtypes_template, byte_code)
+            r.dtypes = mio5p.MDTYPES[byte_code]['dtypes']
             c_reader = m5u.VarReader5(r)
             yield assert_equal, c_reader.little_endian, byte_code == '<'
             yield assert_equal, c_reader.is_swapped, byte_code != boc.native_code
@@ -149,22 +145,22 @@
                 yield assert_equal, el, val
                 el = c_reader.read_numeric()
                 yield assert_equal, el, val
-    
 
+
 def test_read_numeric_writeable():
     # make reader-like thing
     str_io = cStringIO()
     r = _make_readerlike()
     r.mat_stream = str_io
     r.byte_order = '<'
-    r.dtypes = miob.convert_dtypes(mio5.mdtypes_template, '<')
     c_reader = m5u.VarReader5(r)
     dt = np.dtype('<u2')
-    a = _make_tag(dt, 30, mio5.miUINT16, 0)
+    a = _make_tag(dt, 30, mio5p.miUINT16, 0)
     a_str = a.tostring()
     _write_stream(str_io, a_str)
     el = c_reader.read_numeric()
     yield assert_true, el.flags.writeable
-    
+
+
 if __name__ == "__main__":
     run_module_suite()



More information about the Scipy-svn mailing list