Implemented _fn_join, dirname, filename, urljoin in RsyncUrl and tests

Change-Id: I0259b10ae6c0495dfc80491902bd390277d1f619
diff --git a/rsync_url.py b/rsync_url.py
index 83061ff..e4fc55f 100644
--- a/rsync_url.py
+++ b/rsync_url.py
@@ -152,28 +152,56 @@
                 return False
         return True
 
-    def _url_join(self, *suburls):
-        url = self.url
-        if len(url) > 1:
-            while url.endswith(os.path.sep):
-                url = url[:-1]
+    def _fn_join(self, *parts):
+        ''' Joins filenames with ignoring empty parts (None, '', etc)'''
+        parts = [_ for _ in parts if _]
 
-        subs = os.path.sep.join([_ for _ in suburls if _]).split(os.path.sep)
+        if len(parts) > 0:
+            if parts[-1].endswith(os.path.sep):
+                isdir = True
+            else:
+                isdir = False
+            first, parts = parts[0], parts[1:]
+        else:
+            return ''
+
+        if first is None:
+            first = ''
+        if len(first) > 1:
+            while first.endswith(os.path.sep):
+                first = first[:-1]
+
+        subs = os.path.sep.join([_ for _ in parts if _]).split(os.path.sep)
         subs = [_ for _ in subs if _]
 
-        result = re.sub(r'^//', r'/', os.path.sep.join([url, ] + subs))
+        result = re.sub(r'^//', r'/', os.path.sep.join([first, ] + subs))
         result = re.sub(r'([^:])//', r'\1/', result)
+        if not result.endswith(os.path.sep) and isdir:
+            result += os.path.sep
         return result
 
     @property
     def url(self):
         return self._url
 
-    def url_in(self, *path):
-        result = self._url_join(*path)
+    def urljoin(self, *parts):
+        return self._fn_join(self.url, *parts)
+
+    def dirname(self, *path):
+        result = self._fn_join(*path)
         if not result.endswith('/'):
             result += '/'
         return result
 
+    def url_in(self, *path):
+        return self.dirname(self.url, *path)
+
+    def filename(self, *path):
+        result = self._fn_join(*path)
+        if len(result) > 1:
+            while result.endswith(os.path.sep):
+                result = result[:-1]
+        return result
+
     def url_is(self, *path):
-        return self._url_join(*path)
+        return self.filename(self.url, *path)
diff --git a/test_rsync_url.py b/test_rsync_url.py
index 0fd1e5c..74e05ce 100644
--- a/test_rsync_url.py
+++ b/test_rsync_url.py
@@ -62,18 +62,44 @@
         self.log_locals(url)
         self.assertEqual(url.url, expected_result)
 
+    def urljoin(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
+        url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
+        for par, er in expected_result.items():
+            logger.info('par = "{}", er = "{}"'.format(par, er))
+            self.assertEqual(url.urljoin(par), er)
+
+    def dirname(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
+        url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
+        for par, er in expected_result.items():
+            logger.info('par = "{}", er = "{}"'.format(par, er))
+            self.assertEqual(url.dirname(par), er)
+
     def url_in(self, remote, expected_result):
         logger.info('"{}" - {}'.format(remote, expected_result))
         url = rsync_url.RsyncUrl(remote)
         self.log_locals(url)
         for par, er in expected_result.items():
+            logger.info('par = "{}", er = "{}"'.format(par, er))
             self.assertEqual(url.url_in(par), er)
 
+    def filename(self, remote, expected_result):
+        logger.info('"{}" - {}'.format(remote, expected_result))
+        url = rsync_url.RsyncUrl(remote)
+        self.log_locals(url)
+        for par, er in expected_result.items():
+            logger.info('par = "{}", er = "{}"'.format(par, er))
+            self.assertEqual(url.filename(par), er)
+
     def url_is(self, remote, expected_result):
         logger.info('"{}" - {}'.format(remote, expected_result))
         url = rsync_url.RsyncUrl(remote)
         self.log_locals(url)
         for par, er in expected_result.items():
+            logger.info('par = "{}", er = "{}"'.format(par, er))
             self.assertEqual(url.url_is(par), er)
 
 testdata = yaml.load(open('test_rsync_url.yaml'))
diff --git a/test_rsync_url.yaml b/test_rsync_url.yaml
index 1dc42c0..2325811 100644
--- a/test_rsync_url.yaml
+++ b/test_rsync_url.yaml
@@ -5,8 +5,53 @@
 #   test_function_name_3:
 #     - expected
 #     - results
+'ubuntu@172.18.66.89:~/':
+  url: 'ubuntu@172.18.66.89:~/'
+  urljoin:
+    null: 'ubuntu@172.18.66.89:~/'
+    '': 'ubuntu@172.18.66.89:~/'
+    '/': 'ubuntu@172.18.66.89:~/'
+    '/first/level/': 'ubuntu@172.18.66.89:~/first/level/'
+    'first/level': 'ubuntu@172.18.66.89:~/first/level'
+  dirname:
+    null: '/'
+    '': '/'
+    '/': '/'
+    '/first/level/': '/first/level/'
+    'first/level': 'first/level/'
+  filename:
+    null: ''
+    '': ''
+    '/': '/'
+    '/first/level/': '/first/level'
+    'first/level': 'first/level'
+  url_in:
+    null: 'ubuntu@172.18.66.89:~/'
+    '': 'ubuntu@172.18.66.89:~/'
+    '/': 'ubuntu@172.18.66.89:~/'
+    '/first/level/': 'ubuntu@172.18.66.89:~/first/level/'
+    'first/level': 'ubuntu@172.18.66.89:~/first/level/'
+  url_is:
+    null: 'ubuntu@172.18.66.89:~'
+    '': 'ubuntu@172.18.66.89:~'
+    '/': 'ubuntu@172.18.66.89:~'
+    '/first/level/': 'ubuntu@172.18.66.89:~/first/level'
+    'first/level': 'ubuntu@172.18.66.89:~/first/level'
+  exact_match_num: 1
+  classed: 'ssh'
+  parsed:
+    - 'ubuntu'
+    - '172.18.66.89'
+    - '~/'
+  valid: True
 'ubuntu@172.18.66.89:~':
   url: 'ubuntu@172.18.66.89:~'
+  urljoin:
+    null: 'ubuntu@172.18.66.89:~'
+    '': 'ubuntu@172.18.66.89:~'
+    '/': 'ubuntu@172.18.66.89:~/'
+    '/first/level/': 'ubuntu@172.18.66.89:~/first/level/'
+    'first/level': 'ubuntu@172.18.66.89:~/first/level'
   url_in:
     null: 'ubuntu@172.18.66.89:~/'
     '': 'ubuntu@172.18.66.89:~/'
@@ -64,6 +109,12 @@
   valid: True
 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp':
   url: 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp'
+  urljoin:
+    null: 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp'
+    '': 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp'
+    '/': 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp/'
+    '/first/level/': 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp/first/level/'
+    'first/level': 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp/first/level'
   url_in:
     null: 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp/'
     '': 'johnivanov@172.18.66.89::mirror-sync/otlichniy/reg/exp/'
@@ -128,6 +179,12 @@
   #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'
+  urljoin:
+    null: 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp'
+    '': 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp'
+    '/': 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp/'
+    '/first/level/': 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp/first/level/'
+    'first/level': 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp/first/level'
   url_in:
     null: 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp/'
     '': 'rsync://mirror-sync@172.18.66.89:7327/otlichniy/reg/exp/'
@@ -195,6 +252,12 @@
   valid: False
 '/':
   url: '/'
+  urljoin:
+    null: '/'
+    '': '/'
+    '/': '/'
+    '/first/level/': '/first/level/'
+    'first/level': '/first/level'
   url_in:
     null: '/'
     '': '/'