Repository: G-Node/nix-odML-converter

Issue:
[odml-nix-conversion] Cannot specify target NIX file 
Currently content from an odML file will only be appended to a NIX file, if both files have the same filename.

It would be useful to specify a NIX file to which the content of an odML file will be appended.

Discussion:

Patch:
diff --git a/nixodmlconverter/convert.py b/nixodmlconverter/convert.py
index accb945..60702ad 100644
--- a/nixodmlconverter/convert.py
+++ b/nixodmlconverter/convert.py
@@ -9,10 +9,10 @@ Furthermore, the converter b) exports odML content from a NIX file and saves it
 to an XML formatted odML file. If an odML file of the same name exists, the
 file will be overwritten.
 
-Usage: nixodmlconverter [-h] FILE...
+Usage: nixodmlconverter [-h] FILE_OR_DIR [-o OUTFILE | -t TYPE] [-m MODE]
 
 Arguments:
-    FILE            NIX or odML file.
+    FILE_OR_DIR     NIX or odML file or directory.
 
                     If the provided file is a NIX file, the odML content of this NIX file
                     will be exported to an odML file of the same name.
@@ -24,9 +24,22 @@ Arguments:
                     If a NIX file with the same name exists, the content of the odML
                     file will be appended, otherwise, a new NIX file will be created.
 
-                    Multiple files can be provided.
+                    A directory can also be provided. If so, all respective files within
+                    the directory are converted. Without options, nix files are converted
+                    to odML.
 
 Options:
+    -o              Specify name of output file. Only if one file is presented.
+    -t              Convert only files of this type/with this extension.
+                    Supported extensions:
+                        - nix (default)
+                        - xml
+                        - odml
+                    Only, if a directory is presented.
+    -m              Conversion mode:
+                        - append (only for conversion to nix, default setting)
+                        - overwrite (default for nix to odML conversion)
+                        - overwritemetadata (only change nix file's odML metadata)
     -h --help       Show this screen.
     --version       Show version
 """
@@ -36,6 +49,7 @@ import sys
 import re
 
 from docopt import docopt
+import isodate
 
 import nixio as nix
 import odml
@@ -57,6 +71,9 @@ INFO = {"sections read": 0,
         "mod_prop_values": 0,
         "odml_types_omitted": 0}
 
+possible_modes = ['append', 'overwrite', 'overwritemetadata']
+possible_types = ['nix', 'odml', 'xml']
+
 
 def print_info():
     print("\nConversion info")
@@ -77,31 +94,18 @@ def print_info():
           "(using string instead)\n".format(**INFO))
 
 
-def user_input(prompt):
-    """
-    Executes the appropriate user input function dependent on
-    the Python version.
-
-    :param prompt: Information string prompting user input.
-    :return: User input string.
-    """
-    if sys.version_info < (3, 0):
-        return raw_input(prompt)
-
-    return input(prompt)
-
-
 def infer_dtype(values):
     """
     Tests, whether values with dtype "string" are maybe of different dtype.
 
     :param values: values, for which the dtype should be found
+    :return found dtype
     """
 
     dtype_checks = {
         'int': r'^(-+)?\d+$',
         'date': r'^\d{2,4}-\d{1,2}-\d{1,2}$',
-        'datetime': r'^\d{2,4}-\d{1,2}-\d{1,2} \d{2}:\d{2}(:\d{2})?$',
+        'datetime': r'^\d{2,4}-\d{1,2}-\d{1,2} ?T?\d{2}:\d{2}(:\d{2})?([Z|(\+|\-)hh:mm])?$',
         'time': r'^\d{2}:\d{2}(:\d{2})?$',
         'float': r'^(-+)?\d+\.\d+$',
         'tuple': r'^\((.*?)\)',
@@ -131,12 +135,13 @@ def infer_dtype(values):
 
         val_dtypes += [curr_dtype]
 
+    found_dtype = "string"
     if len(set(val_dtypes)) == 1:
-        return val_dtypes[0]
+        found_dtype = val_dtypes[0]
     if "text" in set(val_dtypes):
-        return "text"
+        found_dtype = "text"
 
-    return "string"
+    return found_dtype
 
 
 def non_binary_value(val):
@@ -219,7 +224,8 @@ def odml_to_nix_property(odmlprop, nixsec):
     nixprop.unit = odmlprop.unit
 
     nixprop.definition = odmlprop.definition
-    nixprop.uncertainty = odmlprop.uncertainty
+    if odmlprop.uncertainty:
+        nixprop.uncertainty = float(odmlprop.uncertainty)
     nixprop.reference = odmlprop.reference
     nixprop.value_origin = odmlprop.value_origin
     nixprop.dependency = odmlprop.dependency
@@ -228,7 +234,8 @@ def odml_to_nix_property(odmlprop, nixsec):
     # We also need to provide the appropriate odML data type for a potential
     # later export from NIX to odML.
     try:
-        nixprop.odml_type = nix.property.OdmlType(odmlprop.dtype)
+        if odmlprop.dtype and len(odmlprop.values) > 0:
+            nixprop.odml_type = nix.property.OdmlType(odmlprop.dtype)
     except ValueError:
         print("\n[WARNING] Cannot set odml type {}\n".format(odmlprop.dtype))
         INFO["odml_types_omitted"] += 1
@@ -279,13 +286,13 @@ def write_odml_doc(odmldoc, nixfile):
 
 def nixwrite(odml_doc, filename, mode='append'):
     filemode = None
-    if mode == 'append' or mode == 'overwrite metadata':
+    if mode in ('append', 'overwritemetadata'):
         filemode = nix.FileMode.ReadWrite
     elif mode == 'overwrite':
         filemode = nix.FileMode.Overwrite
 
     with nix.File.open(filename, filemode) as nixfile:
-        if mode == 'overwrite metadata':
+        if mode == 'overwritemetadata':
             if 'odML document' in nixfile.sections:
                 del nixfile.sections['odML document']
 
@@ -378,6 +385,11 @@ def nix_to_odml_property(nixprop, odml_sec):
         else:
             nix_prop_attributes['dtype'] = infer_dtype(non_byte_vals)
 
+    if "datetime" in str(nix_prop_attributes['dtype']):
+        for (i, nbv) in enumerate(non_byte_vals):
+            if "T" in str(nbv):
+                non_byte_vals[i] = isodate.parse_datetime(nbv)
+
     if 'reference' in nix_prop_attributes:
         nix_prop_attributes['reference'] = non_binary_value(nix_prop_attributes.pop('reference'))
 
@@ -409,7 +421,7 @@ def nix_to_odml_recurse(nix_section_list, odml_section):
         nix_to_odml_recurse(nix_sec.sections, odml_sec)
 
 
-def convert(filename, mode='append'):
+def convert(filename, outfilename=None, mode='append'):
     # Determine input and output format
     file_base, file_ext = os.path.splitext(filename)
     if file_ext in ['.xml', '.odml']:
@@ -424,10 +436,14 @@ def convert(filename, mode='append'):
         mode = 'overwrite'
 
     # Check output file
-    outfilename = file_base + output_format
+    if not outfilename:
+        outfilename = file_base
+    if not output_format in outfilename:
+        outfilename += output_format
+
     if os.path.exists(outfilename):
-        yesno = user_input("File {} already exists. "
-                           "{} (y/n)? ".format(outfilename, mode.title()))
+        yesno = input("File {} already exists. "
+                      "{} (y/n)? ".format(outfilename, mode.title()))
         if yesno.lower() not in ("y", "yes"):
             print("Aborted")
             return
@@ -439,9 +455,9 @@ def convert(filename, mode='append'):
             odml_doc = odml.load(filename)
         except InvalidVersionException:
 
-            yesno = user_input("odML file format version is outdated. "
-                               "Automatically convert {} to the latest version "
-                               "(y/n)? ".format(outfilename))
+            yesno = input("odML file format version is outdated. "
+                          "Automatically convert {} to the latest version "
+                          "(y/n)? ".format(outfilename))
 
             if yesno.lower() not in ("y", "yes"):
                 print("  Use the odml.tools.VersionConverter to convert "
@@ -466,10 +482,22 @@ def convert(filename, mode='append'):
 def main(args=None):
     parser = docopt(__doc__, argv=args, version=VERSION)
 
-    files = parser['FILE']
-    print(files)
-    for curr_file in files:
-        convert(curr_file)
+    file_or_dir = parser['FILE_OR_DIR']
+    mode = parser['MODE'] if parser['MODE'] else 'append'
+    if mode not in possible_modes:
+        raise NameError('"{}" is not a valid conversion mode. Possible modes are: {}'.format(mode, possible_modes))
+    if not os.path.splitext(file_or_dir)[1]:
+        file_type = parser['TYPE'] if parser['TYPE'] else "nix"
+        if file_type not in possible_types:
+            raise NameError('"{}" is not a valid file extension. Possible extensions are: {}'
+                            .format(file_type, possible_types))
+        for curr_file in os.listdir(file_or_dir):
+            if curr_file.endswith("." + file_type):
+                print("Found File {}".format(curr_file))
+                convert(filename=os.path.join(file_or_dir, curr_file), mode=mode)
+    else:
+        outfile = parser['OUTFILE']
+        convert(filename=file_or_dir, outfilename=outfile, mode=mode)
     print_info()
 
 
diff --git a/setup.py b/setup.py
index 5ce24ca..83cb94b 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,7 @@ with open('README.md', encoding="utf8") as f:
 
 # Older Python installations might come with a "six" version<1.12.0
 # for which nixio will fail.
-install_req = ["odml>=1.4.5", "nixio>=1.5.0b1", "docopt", "six>=1.12.0"]
+install_req = ["odml>=1.4.5", "nixio>=1.5.0b1", "docopt", "six>=1.12.0", "isodate"]
 
 if sys.version_info < (3, 4):
     install_req += ["enum34"]


Test patch:
diff --git a/test/test_doc.py b/test/test_doc.py
index 378ec40..43e14e4 100644
--- a/test/test_doc.py
+++ b/test/test_doc.py
@@ -58,7 +58,7 @@ class TestBlock(unittest.TestCase):
         orig_odml_doc = odml.Document(author=None, date=None,
                                       version=None, repository=None)
 
-        convert.nixwrite(orig_odml_doc, nix_path, 'overwrite')
+        convert.nixwrite(orig_odml_doc, nix_path, nix.FileMode.Overwrite)
 
         nix_file_r = nix.File.open(nix_path, nix.FileMode.ReadOnly)
         convert.odmlwrite(nix_file_r, odml_path)
@@ -78,7 +78,7 @@ class TestBlock(unittest.TestCase):
 
         orig_odml_doc = odml.Document()
 
-        convert.nixwrite(orig_odml_doc, nix_path, 'overwrite')
+        convert.nixwrite(orig_odml_doc, nix_path, nix.FileMode.Overwrite)
 
         nix_file_r = nix.File.open(nix_path, nix.FileMode.ReadOnly)
         convert.odmlwrite(nix_file_r, odml_path)
diff --git a/test/test_dtypes.py b/test/test_dtypes.py
index 40dae8e..4fadf8a 100644
--- a/test/test_dtypes.py
+++ b/test/test_dtypes.py
@@ -533,7 +533,7 @@ class TestDtypes(unittest.TestCase):
 
         sec_1 = nix_file_1.create_section(name="section")
         prop_1 = sec_1.create_property(name="datetime property", values_or_dtype="datetime")
-        prop_1.values = ['2011-11-01 11:11:11', '2012-12-02 02:02:02']
+        prop_1.values = ['2011-11-01 11:11:11', '2012-12-02 02:02:02', '2012-12-03T03:03:03']
 
         convert.odmlwrite(nix_file_1, odml_path_1)
         odml_doc_1 = odml.load(odml_path_1)
@@ -541,9 +541,10 @@ class TestDtypes(unittest.TestCase):
         odml_prop_1 = odml_doc_1.sections[0].props[0]
         vals_1 = odml_prop_1.values
         self.assertEqual(getattr(odml_prop_1, "dtype"), odml.DType.datetime)
-        self.assertEqual(len(vals_1), 2)
+        self.assertEqual(len(vals_1), 3)
         self.assertEqual(vals_1, [datetime.datetime(2011, 11, 1, 11, 11, 11),
-                                datetime.datetime(2012, 12, 2, 2, 2, 2)])
+                                datetime.datetime(2012, 12, 2, 2, 2, 2),
+                                datetime.datetime(2012, 12, 3, 3, 3, 3)])
         nix_file_1.close()
 
         file_name_2 = 'tmp' + str(uuid.uuid4())
@@ -554,7 +555,7 @@ class TestDtypes(unittest.TestCase):
         sec_2 = nix_file_2.create_section(name="section")
 
         prop_2 = sec_2.create_property(name="datetime property 2", values_or_dtype=np.str_)
-        prop_2.values = ['2012-12-02 12:12:12', '2013-01-01 01:01:01']
+        prop_2.values = ['2012-12-02 12:12:12', '2013-01-01 01:01:01', '2013-01-02T02:02:02']
         setattr(prop_2, "odml_type", nix.OdmlType("datetime"))
 
         convert.odmlwrite(nix_file_2, odml_path_2)
@@ -563,9 +564,10 @@ class TestDtypes(unittest.TestCase):
         odml_prop_2 = odml_doc_2.sections[0].props[0]
         vals = odml_prop_2.values
         self.assertEqual(getattr(odml_prop_2, "dtype"), odml.DType.datetime)
-        self.assertEqual(len(vals), 2)
+        self.assertEqual(len(vals), 3)
         self.assertEqual(vals, [datetime.datetime(2012, 12, 2, 12, 12, 12),
-                                datetime.datetime(2013, 1, 1, 1, 1, 1)])
+                                datetime.datetime(2013, 1, 1, 1, 1, 1),
+                                datetime.datetime(2013, 1, 2, 2, 2, 2)])
         nix_file_2.close()
 
     def test_nix_to_odml_text(self):
diff --git a/test/test_property.py b/test/test_property.py
index e70e843..dfe0f44 100644
--- a/test/test_property.py
+++ b/test/test_property.py
@@ -87,7 +87,6 @@ class TestBlock(unittest.TestCase):
     def test_nix_to_odml_none(self):
         file_name = 'tmp'
         nix_path = os.path.join(self.test_dir, file_name + '.nix')
-        nix_file_w = nix.File.open(nix_path, nix.FileMode.Overwrite)
         odml_path = os.path.join(self.test_dir, file_name + '.xml')
 
         odml.Property(name=None, oid=None, definition=None,
@@ -97,8 +96,7 @@ class TestBlock(unittest.TestCase):
                       reference=None, dependency=None,
                       dependency_value=None, value_origin=None)
 
-        convert.nixwrite(self.odml_doc, nix_path)
-        nix_file_w.close()
+        convert.nixwrite(self.odml_doc, nix_path, nix.FileMode.Overwrite)
 
         nix_file_r = nix.File.open(nix_path, nix.FileMode.ReadOnly)
         convert.odmlwrite(nix_file_r, odml_path)
@@ -125,13 +123,11 @@ class TestBlock(unittest.TestCase):
     def test_nix_to_odml_empty(self):
         file_name = 'tmp'
         nix_path = os.path.join(self.test_dir, file_name + '.nix')
-        nix_file_w = nix.File.open(nix_path, nix.FileMode.Overwrite)
         odml_path = os.path.join(self.test_dir, file_name + '.xml')
 
         odml.Property(parent=self.odml_doc.sections[0])
 
-        convert.nixwrite(self.odml_doc, nix_path)
-        nix_file_w.close()
+        convert.nixwrite(self.odml_doc, nix_path, nix.FileMode.Overwrite)
 
         nix_file_r = nix.File.open(nix_path, nix.FileMode.ReadOnly)
         convert.odmlwrite(nix_file_r, odml_path)
diff --git a/test/test_section.py b/test/test_section.py
index 5fa2c3a..e977ce1 100644
--- a/test/test_section.py
+++ b/test/test_section.py
@@ -75,7 +75,6 @@ class TestBlock(unittest.TestCase):
     def test_nix_to_odml_none(self):
         file_name = 'tmp'
         nix_path = os.path.join(self.test_dir, file_name + '.nix')
-        nix_file_w = nix.File.open(nix_path, nix.FileMode.Overwrite)
         odml_path = os.path.join(self.test_dir, file_name + '.xml')
 
         odml.Section(name=None, oid=None, definition=None,
@@ -83,8 +82,7 @@ class TestBlock(unittest.TestCase):
                      reference=None, repository=None,
                      link=None, include=None)
 
-        convert.nixwrite(self.odml_doc, nix_path)
-        nix_file_w.close()
+        convert.nixwrite(self.odml_doc, nix_path, nix.FileMode.Overwrite)
 
         nix_file_r = nix.File.open(nix_path, nix.FileMode.ReadOnly)
         convert.odmlwrite(nix_file_r, odml_path)
@@ -106,13 +104,11 @@ class TestBlock(unittest.TestCase):
     def test_nix_to_odml_empty(self):
         file_name = 'tmp'
         nix_path = os.path.join(self.test_dir, file_name + '.nix')
-        nix_file_w = nix.File.open(nix_path, nix.FileMode.Overwrite)
         odml_path = os.path.join(self.test_dir, file_name + '.xml')
 
         odml.Section(parent=self.odml_doc)
 
-        convert.nixwrite(self.odml_doc, nix_path)
-        nix_file_w.close()
+        convert.nixwrite(self.odml_doc, nix_path, nix.FileMode.Overwrite)
 
         nix_file_r = nix.File.open(nix_path, nix.FileMode.ReadOnly)
         convert.odmlwrite(nix_file_r, odml_path)