Updated rsync_url and tests

Change-Id: I652a72713ab82661232db20b9643d3340e016af0
diff --git a/rsync_url.py b/rsync_url.py
index 38836ac..4f45859 100644
--- a/rsync_url.py
+++ b/rsync_url.py
@@ -11,7 +11,7 @@
 
     def __init__(self, remote_url):
 
-        self.url = remote_url
+        self._url = remote_url
         self._url_type = False
 
         self.regexps = {
@@ -30,7 +30,8 @@
                 r'(?P<user>[-\w]+@)?'
                 r'(?P<host>[-\.\w]+){1}'
                 r'::'
-                r'(?P<path>[\w/-]*){1}'
+                r'(?P<module>[\w-]+){1}'
+                r'(?P<path>[\w/-]*)?'
                 r'$'
             ),
             # rsync://[USER@]HOST[:PORT]/SRC
@@ -39,7 +40,8 @@
                 r'(?P<user>[-\w]+@)?'
                 r'(?P<host>[-\.\w]+){1}'
                 r'(?P<port>:[\d]+)?'
-                r'(?P<path>[\w/-]*){1}'
+                r'(?P<module>/[\w-]*)?'
+                r'(?P<path>[\w/-]*)?'
                 r'$'
             ),
             # local/path/to/directory
@@ -50,9 +52,10 @@
             ),
         }
 
-        self.match = self._get_matching_regexp()
+        self._match = self._get_matching_regexp()
         if self.match is None:
-            self.user, self.host, self.port, self.path = None, None, None, None
+            self.user, self.host, self.module, self.port, self.path = \
+                None, None, None, None, None
         else:
             self._parse_rsync_url(self.match)
 
@@ -83,17 +86,16 @@
                 if self.url_type is False:
                     self._url_type = url_type
                 regexps.append(regexp)
-            #print match, regexp.pattern
         return regexps
 
     def _parse_rsync_url(self, regexp):
         # parse remote url
 
-        for match in re.finditer(regexp, self.url):
+        for match in re.finditer(regexp, self._url):
 
             self.path = match.group('path')
-            if not self.path:
-                self.path = '/'
+            if self.path is None:
+                self.path = ''
 
             try:
                 self.host = match.group('host')
@@ -116,6 +118,38 @@
                 if self.port is not None:
                     self.port = int(self.port.strip(':'))
 
+            try:
+                self.module = match.group('module')
+            except IndexError:
+                self.module = None
+            else:
+                if self.module is not None:
+                    self.module = self.module.strip('/')
+                if not self.module:
+                    self.module = None
+
+    @property
+    def match(self):
+        return self._match
+
     @property
     def url_type(self):
         return self._url_type
+
+    @property
+    def is_valid(self):
+        if self.match is None:
+            return False
+        if self.path in (None, False):
+            return False
+        if self.url_type != 'path':
+            if self.host in ('', None, False):
+                return False
+        if self.url_type.startswith('rsync'):
+            if self.module is None:
+                return False
+        return True
+
+    @property
+    def url(self):
+        return self._url
diff --git a/test_rsync_url.py b/test_rsync_url.py
index 5166b6c..cb6c350 100644
--- a/test_rsync_url.py
+++ b/test_rsync_url.py
@@ -1,28 +1,67 @@
 #-*- coding: utf-8 -*-
 
-import rsync_url
+import logging
 import unittest
 import yaml
 
+import rsync_url
+
+
+logging.basicConfig(level='INFO')
+logger = logging.getLogger('TestRsyncUrl')
+
 
 class TestRsyncUrl(unittest.TestCase):
 
+    def log_locals(self, url):
+        if url.match:
+            logger.info('RE: "{}"'.format(url.match.pattern))
+        logger.info('user "{}", host "{}", port "{}", module "{}", '
+                    'path "{}"'.format(url.user, url.host, url.port,
+                                       url.module, url.path))
+
     def exact_match_num(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
         url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
         matching_regexps = url._get_all_matching_regexps()
         self.assertEqual(len(matching_regexps), expected_result)
 
     def classed(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
         url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
         self.assertEqual(url.url_type, expected_result)
 
     def parsed(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
         url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
         self.assertEqual(
-            [url.user, url.host, url.port, url.path],
+            [url.user, url.host, url.path],
             expected_result
         )
 
+    def parsed_rsync(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
+        url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
+        self.assertEqual(
+            [url.user, url.host, url.port, url.module, url.path],
+            expected_result
+        )
+
+    def valid(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
+        url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
+        self.assertEqual(url.is_valid, expected_result)
+
+    def url(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
+        url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
+        self.assertEqual(url.url, expected_result)
 
 testdata = yaml.load(open('test_rsync_url.yaml'))
 
@@ -38,7 +77,7 @@
             getattr(self, test)(remote, expected_result)
 
         test_function.__name__ = \
-            'test_{}_{}_{}'.format(index, tests['classed'], test)
+            'test_rsync_url_{}_{}_{}'.format(index, tests['classed'], test)
         test_function.__doc__ = test_function.__name__
         setattr(TestRsyncUrl, test_function.__name__, test_function)
         del test_function
diff --git a/test_rsync_url.yaml b/test_rsync_url.yaml
index d784da3..eaac8dd 100644
--- a/test_rsync_url.yaml
+++ b/test_rsync_url.yaml
@@ -6,154 +6,191 @@
 #     - expected
 #     - results
 'ubuntu@172.18.66.89:~':
+  url: 'ubuntu@172.18.66.89:~'
   exact_match_num: 1
   classed: 'ssh'
   parsed:
     - 'ubuntu'
     - '172.18.66.89'
-    - null
     - '~'
+  valid: True
 'johnivanov@172.18.66.89:/mirror-sync/otlichniy/reg/exp':
+  url: 'johnivanov@172.18.66.89:/mirror-sync/otlichniy/reg/exp'
   exact_match_num: 1
   classed: 'ssh'
   parsed:
     - 'johnivanov'
     - '172.18.66.89'
-    - null
     - '/mirror-sync/otlichniy/reg/exp'
+  valid: True
 '172.18.66.89:/mirror-sync/otlichniy/reg/exp':
+  url: '172.18.66.89:/mirror-sync/otlichniy/reg/exp'
   exact_match_num: 1
   classed: 'ssh'
   parsed:
     - null
     - '172.18.66.89'
-    - null
     - '/mirror-sync/otlichniy/reg/exp'
+  valid: True
 '172.18.66.89:/':
+  url: '172.18.66.89:/'
   exact_match_num: 1
   classed: 'ssh'
   parsed:
     - null
     - '172.18.66.89'
-    - null
     - '/'
+  valid: True
 '172.18.66.89:':
+  url: '172.18.66.89:'
   exact_match_num: 1
   classed: 'ssh'
   parsed:
     - null
     - '172.18.66.89'
-    - null
-    - '/'
-'johnivanov@172.18.66.89::/mirror-sync/otlichniy/reg/exp':
+    - ''
+  valid: True
+'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp':
+  url: 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp'
   exact_match_num: 1
   classed: 'rsync1'
-  parsed:
+  parsed_rsync:
     - 'johnivanov'
     - '172.18.66.89'
     - null
-    - '/mirror-sync/otlichniy/reg/exp'
+    - 'mirror-sync'
+    - '/otlichniy/reg/exp'
+  valid: True
+'172.18.66.89::mirror-sync/otlichniy/reg/exp':
+  url: '172.18.66.89::mirror-sync/otlichniy/reg/exp'
+  exact_match_num: 1
+  classed: 'rsync1'
+  parsed_rsync:
+    - null
+    - '172.18.66.89'
+    - null
+    - 'mirror-sync'
+    - '/otlichniy/reg/exp'
+  valid: True
+'johnivanov@172.18.66.89::/mirror-sync/otlichniy/reg/exp':
+  url: 'johnivanov@172.18.66.89::/mirror-sync/otlichniy/reg/exp'
+  exact_match_num: 0
+  classed: null
+  valid: False
 '172.18.66.89::/mirror-sync/otlichniy/reg/exp':
-  exact_match_num: 1
-  classed: 'rsync1'
-  parsed:
-    - null
-    - '172.18.66.89'
-    - null
-    - '/mirror-sync/otlichniy/reg/exp'
+  url: '172.18.66.89::/mirror-sync/otlichniy/reg/exp'
+  exact_match_num: 0
+  classed: null
+  valid: False
 '172.18.66.89::/':
-  exact_match_num: 1
-  classed: 'rsync1'
-  parsed:
-    - null
-    - '172.18.66.89'
-    - null
-    - '/'
+  url: '172.18.66.89::/'
+  exact_match_num: 0
+  classed: null
+  valid: False
 '172.18.66.89::':
+  url: '172.18.66.89::'
   exact_match_num: 1
   classed: 'rsync1'
-  parsed:
+  parsed_rsync:
     - null
     - '172.18.66.89'
     - null
-    - '/'
+    - null
+    - ''
+  valid: False
 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp':
+  url: 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp'
   exact_match_num: 1
   classed: 'rsync2'
-  parsed:
+  parsed_rsync:
     - 'mirror-sync'
     - '172.18.66.89'
     - 7327
-    - '/otlichniy/reg/exp'
+    - 'otlichniy'
+    - '/reg/exp'
+  valid: True
 'rsync://172.18.66.89:7327/mirror-sync/otlichniy/reg/exp':
+  url: 'rsync://172.18.66.89:7327/mirror-sync/otlichniy/reg/exp'
   exact_match_num: 1
   classed: 'rsync2'
-  parsed:
+  parsed_rsync:
     - null
     - '172.18.66.89'
     - 7327
-    - '/mirror-sync/otlichniy/reg/exp'
+    - 'mirror-sync'
+    - '/otlichniy/reg/exp'
+  valid: True
 'rsync://172.18.66.89/mirror-sync/otlichniy/reg/exp':
+  url: 'rsync://172.18.66.89/mirror-sync/otlichniy/reg/exp'
   exact_match_num: 1
   classed: 'rsync2'
-  parsed:
+  parsed_rsync:
     - null
     - '172.18.66.89'
     - null
-    - '/mirror-sync/otlichniy/reg/exp'
+    - 'mirror-sync'
+    - '/otlichniy/reg/exp'
+  valid: True
 'rsync://172.18.66.89/':
+  url: 'rsync://172.18.66.89/'
   exact_match_num: 1
   classed: 'rsync2'
-  parsed:
+  parsed_rsync:
     - null
     - '172.18.66.89'
     - null
-    - '/'
+    - null
+    - ''
+  valid: False
 'rsync://172.18.66.89':
+  url: 'rsync://172.18.66.89'
   exact_match_num: 1
   classed: 'rsync2'
-  parsed:
+  parsed_rsync:
     - null
     - '172.18.66.89'
     - null
-    - '/'
+    - null
+    - ''
+  valid: False
 '/':
+  url: '/'
   exact_match_num: 1
   classed: 'path'
   parsed:
     - null
     - null
-    - null
     - '/'
+  valid: True
 'dir':
+  url: 'dir'
   exact_match_num: 1
   classed: 'path'
   parsed:
     - null
     - null
-    - null
     - 'dir'
+  valid: True
 '/dir':
+  url: '/dir'
   exact_match_num: 1
   classed: 'path'
   parsed:
     - null
     - null
-    - null
     - '/dir'
+  valid: True
 '/dir/subdir/':
+  url: '/dir/subdir/'
   exact_match_num: 1
   classed: 'path'
   parsed:
     - null
     - null
-    - null
     - '/dir/subdir/'
+  valid: True
 '':
+  url: ''
   exact_match_num: 0
   classed: null
-  parsed:
-    - null
-    - null
-    - null
-    - null
+  valid: False