spv-dev team mailing list archive
-
spv-dev team
-
Mailing list archive
-
Message #00013
lp:~claude-bolduc/slidepresenterview/refactor-pres-design-claude into lp:~bouf10/slidepresenterview/refactor-pres-design
C. Bolduc has proposed merging lp:~claude-bolduc/slidepresenterview/refactor-pres-design-claude into lp:~bouf10/slidepresenterview/refactor-pres-design.
Requested reviews:
SlidePresenterView Development Team (spv-dev)
Refactoring of the Uri class. It now separates logic for local file URIs and general URIs.
Also added factories to help create the right URIs.
--
https://code.launchpad.net/~claude-bolduc/slidepresenterview/refactor-pres-design-claude/+merge/26560
Your team SlidePresenterView Development Team is requested to review the proposed merge of lp:~claude-bolduc/slidepresenterview/refactor-pres-design-claude into lp:~bouf10/slidepresenterview/refactor-pres-design.
=== modified file 'slidepresenterview/presentations.py'
--- slidepresenterview/presentations.py 2010-05-09 18:15:55 +0000
+++ slidepresenterview/presentations.py 2010-06-02 05:43:25 +0000
@@ -27,8 +27,6 @@
##########
# Imports
#####
-import os
-
from PyQt4 import QtGui
from slidepresenterview.utils.uri import Uri
@@ -50,13 +48,7 @@
def is_presenting(self, other_uri):
- if other_uri.scheme != "file":
- return self.uri == other_uri
-
- try:
- return os.path.samefile(self.uri.path, other_uri.path)
- except OSError: # File doesn't exist. Use a string comparison.
- return self.uri.path == other_uri.path
+ return self.uri == other_uri
class EmptyPresentaton(Presentation):
=== modified file 'slidepresenterview/session.py'
--- slidepresenterview/session.py 2010-05-09 18:15:55 +0000
+++ slidepresenterview/session.py 2010-06-02 05:43:25 +0000
@@ -4,6 +4,7 @@
# https://launchpad.net/slidepresenterview
#
# Copyright (C) 2010 Felix-Antoine Bourbonnais <bouf10pub _AT@. rubico.info>
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,7 +23,7 @@
See L{Session}
"""
-from slidepresenterview.utils.uri import file_path_to_uri
+from slidepresenterview.utils.uri import create_uri_from_file_path
from slidepresenterview.presentations import FakeFilePresentation
from slidepresenterview.presentations import EmptyPresentaton
@@ -65,7 +66,7 @@
@param file_path: The presentation local file
@type file_name: Unicode string (Python)
"""
- file_uri = file_path_to_uri(file_path)
+ file_uri = create_uri_from_file_path(file_path)
if not self.current_presentation.is_presenting(file_uri):
self.current_presentation = FakeFilePresentation(file_uri)
self._notify_presentation_opened()
=== modified file 'slidepresenterview/tests/unit/test_session.py'
--- slidepresenterview/tests/unit/test_session.py 2010-05-09 18:15:55 +0000
+++ slidepresenterview/tests/unit/test_session.py 2010-06-02 05:43:25 +0000
@@ -4,6 +4,7 @@
# https://launchpad.net/slidepresenterview
#
# Copyright (C) 2009 F.-A. Bourbonnais <bouf10pub _AT@. rubico.info>
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,7 +27,7 @@
from mock import Mock
-from slidepresenterview.utils.uri import file_path_to_uri
+from slidepresenterview.utils.uri import create_uri_from_file_path
from slidepresenterview.session import Session
from slidepresenterview.session import SessionObserver
@@ -115,7 +116,8 @@
pres = self.session.current_presentation
self.assertFalse(self._is_default_presentation())
- self.assertTrue(pres.is_presenting(file_path_to_uri('mock.pdf')))
+ self.assertTrue(pres.is_presenting(
+ create_uri_from_file_path('mock.pdf')))
self.assertTrue(self._has_notified_opening_once(pres))
@@ -149,7 +151,7 @@
self.assertNotEquals(self.pres_before, pres_after)
self.assertTrue(pres_after.is_presenting(
- file_path_to_uri('mock_other.pdf')))
+ create_uri_from_file_path('mock_other.pdf')))
self.assertTrue(self._has_notified_opening_once(pres_after))
=== modified file 'slidepresenterview/tests/unit/utils/tests_uri.py'
--- slidepresenterview/tests/unit/utils/tests_uri.py 2010-05-17 04:37:32 +0000
+++ slidepresenterview/tests/unit/utils/tests_uri.py 2010-06-02 05:43:25 +0000
@@ -4,6 +4,7 @@
# https://launchpad.net/slidepresenterview
#
# Copyright (C) 2010 Felix-Antoine Bourbonnais <bouf10pub _AT@. rubico.info>
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,12 +23,16 @@
Tests for the L{slidepresenterview.utils.uri} module.
"""
+import os
+from os.path import normpath as os_path_normpath
+from os.path import splitdrive as os_path_splitdrive
import unittest
from mock import patch, Mock
-from slidepresenterview.utils.uri import file_path_to_uri
-from slidepresenterview.utils.uri import Uri
+from slidepresenterview.utils.uri import create_uri_from_uri_string
+from slidepresenterview.utils.uri import create_uri_from_file_path
+from slidepresenterview.utils.uri import Uri, LocalFileUri
import sys
@@ -37,14 +42,126 @@
_encoding = 'UTF-8'
-def _fake_abspath(path):
+# Helper methods to create Mocks for paths in different OSes.
+
+def _fake_os_path_unix():
+ module = Mock(spec=os.path)
+ module.abspath = Mock(side_effect=_fake_abspath_unix)
+ module.expanduser = Mock(side_effect=_fake_expanduser_unix)
+ module.expandvars = Mock(side_effect=_fake_expandvars_unix)
+ module.normpath = Mock(side_effect=_fake_normpath_unix)
+ module.normcase = Mock(side_effect=_fake_normcase_unix)
+ module.samefile = Mock(side_effect=_fake_samefile_unix)
+ module.splitdrive = Mock(side_effect=_fake_splitdrive_unix)
+ return module
+
+
+def _fake_abspath_unix(path):
if len(path) == 0 or path[0] != '/':
return '/root/%s' % path
return path
-
-class TestFilePath_UnixToUri(unittest.TestCase): #pylint: disable-msg=R0904
+def _fake_expanduser_unix(path):
+ if len(path) > 0 and path[0] == '~':
+ return '/home/user/%s' % path[1:]
+ return path
+
+
+def _fake_expandvars_unix(path):
+ path = path.replace('$USER','user')
+ return path
+
+
+def _fake_normpath_unix(path):
+ return os_path_normpath(path)
+
+
+def _fake_normcase_unix(path):
+ return path
+
+
+def _fake_samefile_unix(path, other_path):
+ return (other_path != 'not_exists.pdf'
+ and other_path != u"/path/a e/fé.pdf")
+
+
+def _fake_splitdrive_unix(path):
+ return os_path_splitdrive(path)
+
+
+def _fake_os_path_windows():
+ module = Mock(spec=os.path)
+ module.abspath = Mock(side_effect=_fake_abspath_windows)
+ module.expanduser = Mock(side_effect=_fake_expanduser_windows)
+ module.expandvars = Mock(side_effect=_fake_expandvars_windows)
+ module.normpath = Mock(side_effect=_fake_normpath_windows)
+ module.normcase = Mock(side_effect=_fake_normcase_windows)
+ module.splitdrive = Mock(side_effect=_fake_splitdrive_windows)
+ return module
+
+
+def _fake_abspath_windows(path):
+ drive, _ = os.path.splitdrive(path)
+ if drive == '':
+ return 'c:/documents and settings/%s' % path
+ return path
+
+
+def _fake_expanduser_windows(path):
+ if len(path) > 0 and path[0] == '~':
+ return 'c:/documents and settings/user%s' % path[1:]
+ return path
+
+
+def _fake_expandvars_windows(path):
+ path = path.replace('%UserProfile%','c:/documents and settings/user')
+ return path
+
+
+def _fake_normpath_windows(path):
+ path = path.replace('\\', '/')
+ return os_path_normpath(path)
+
+
+def _fake_normcase_windows(path):
+ return path.lower()
+
+
+def _fake_splitdrive_windows(path):
+ return os_path_splitdrive(path)
+
+
+#pylint: disable-msg=R0904
+class TestCreateUriFromUriString(unittest.TestCase):
+
+ def setUp(self):
+ self.local_file_uri_win = u"file:///c:/path/a é/fé.pdf"
+ self.network_file_uri = u"file://localhost/path/fé.pdf"
+ self.http_uri = u"http://éxample.com/patéh?a=é"
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_should_create_LocalFileUri_when_receiving_local_file_uri_string(
+ self):
+ uri = create_uri_from_uri_string(self.local_file_uri_win)
+ self.assertTrue(isinstance(uri, LocalFileUri))
+
+
+ def test_should_create_Uri_when_receiving_network_file_uri_string(self):
+ uri = create_uri_from_uri_string(self.network_file_uri)
+ self.assertFalse(isinstance(uri, LocalFileUri))
+ self.assertTrue(isinstance(uri, Uri))
+
+
+ def test_should_create_Uri_when_receiving_http_uri_string(self):
+ uri = create_uri_from_uri_string(self.http_uri)
+ self.assertFalse(isinstance(uri, LocalFileUri))
+ self.assertTrue(isinstance(uri, Uri))
+
+
+#pylint: disable-msg=R0904
+class TestCreateUriFromFilePathUnix(unittest.TestCase):
def setUp(self):
self.path_filename = 'file.pdf'
@@ -52,51 +169,147 @@
self.path_abs = '/complete/path/to/file.pdf'
self.path_unicode = u'sé/filé.pdf'
self.path_not_norm = '//dir/../dir2/./a//file.pdf'
+ self.path_home = '~/file.pdf'
+ self.path_var = '/home/$USER/file.pdf'
- @patch('os.path.abspath', Mock(side_effect=_fake_abspath))
+ @patch('os.path', _fake_os_path_unix())
def test_empty_path_should_be_the_current_dir(self):
- uri = file_path_to_uri('')
+ uri = create_uri_from_file_path('')
self.assertEquals('file:///root', uri.uri)
+
- @patch('os.path.abspath', Mock(side_effect=_fake_abspath))
- def should_procude_a_valid_uri(self):
- uri = file_path_to_uri(self.path_abs)
+ @patch('os.path', _fake_os_path_unix())
+ def should_produce_a_valid_uri(self):
+ uri = create_uri_from_file_path(self.path_abs)
self.assertEquals('file:///complete/path/to/file.pdf', uri.uri)
- @patch('os.path.abspath', Mock(side_effect=_fake_abspath))
+ @patch('os.path', _fake_os_path_unix())
def test_filename_should_return_an_absolute_path(self):
- uri = file_path_to_uri(self.path_filename)
+ uri = create_uri_from_file_path(self.path_filename)
self.assertEquals('/root/file.pdf', uri.path)
- @patch('os.path.abspath', Mock(side_effect=_fake_abspath))
+ @patch('os.path', _fake_os_path_unix())
def test_relative_path_should_return_an_absolute_path(self):
- uri = file_path_to_uri(self.path_rel)
+ uri = create_uri_from_file_path(self.path_rel)
self.assertEquals('/root/sub/file.pdf', uri.path)
- @patch('os.path.abspath', Mock(side_effect=_fake_abspath))
- def should_be_normalised(self):
- uri = file_path_to_uri(self.path_not_norm)
+ @patch('os.path', _fake_os_path_unix())
+ def should_be_normalized(self):
+ uri = create_uri_from_file_path(self.path_not_norm)
self.assertEquals('//dir2/a/file.pdf', uri.path)
+
+ @patch('os.path', _fake_os_path_unix())
+ def should_be_expanded_for_user(self):
+ uri = create_uri_from_file_path(self.path_home)
+ self.assertEquals('/home/user/file.pdf', uri.path)
+
+
+ @patch('os.path', _fake_os_path_unix())
+ def should_be_expanded_for_environment_variables(self):
+ uri = create_uri_from_file_path(self.path_var)
+ self.assertEquals('/home/user/file.pdf', uri.path)
+
- @patch('os.path.abspath', Mock(side_effect=_fake_abspath))
+ @patch('os.path', _fake_os_path_unix())
def should_accept_unicode(self):
- uri = file_path_to_uri(self.path_unicode)
+ uri = create_uri_from_file_path(self.path_unicode)
self.assertEquals(u'/root/sé/filé.pdf', uri.path)
-class TestFilePath_WinToUri(unittest.TestCase):
+ @patch('os.path', _fake_os_path_unix())
+ def should_have_correct_original_path(self):
+ uri = create_uri_from_file_path(self.path_rel)
+ self.assertEquals('sub/file.pdf', uri.original_path)
+
+
+class TestCreateUriFromFilePathWindows(unittest.TestCase):
def setUp(self):
- self.path_win = u'c:/document and settings/aée/My Documents/file.pdf'
- self.path_win_not_norm = u'c:\document and settings/a\\ée//file.pdf'
-
- def should_todo(self):
- self.fail("Not implemented.")
+ self.path_filename = 'file.pdf'
+ self.path_rel = 'sub/file.pdf'
+ self.path_abs = u'c:/documents and settings/aée/My Documents/file.pdf'
+ self.path_not_norm = u'C:\\Documents and Settings/a\\..\\Ãe//file.pdf'
+ self.path_home = '~\\file.pdf'
+ self.path_var = '%UserProfile%\\file.pdf'
+ self.path_short = 'c:/docum~1'
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_empty_path_should_be_the_current_dir(self):
+ uri = create_uri_from_file_path('')
+ self.assertEquals('file:///c:/documents and settings', uri.uri)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_produce_a_valid_uri(self):
+ uri = create_uri_from_file_path(self.path_abs)
+ self.assertEquals(
+ u'file:///c:/documents and settings/aée/my documents/file.pdf',
+ uri.uri)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_path_should_return_a_path_without_slash_at_first_position(self):
+ uri = create_uri_from_file_path(self.path_abs)
+ self.assertEquals(
+ u'c:/documents and settings/aée/my documents/file.pdf',
+ uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_filename_should_return_an_absolute_path(self):
+ uri = create_uri_from_file_path(self.path_filename)
+ self.assertEquals('c:/documents and settings/file.pdf', uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_relative_path_should_return_an_absolute_path(self):
+ uri = create_uri_from_file_path(self.path_rel)
+ self.assertEquals('c:/documents and settings/sub/file.pdf', uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_be_normalized(self):
+ uri = create_uri_from_file_path(self.path_not_norm)
+ self.assertEquals(u"c:/documents and settings/ée/file.pdf",
+ uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_be_expanded_for_user(self):
+ uri = create_uri_from_file_path(self.path_home)
+ self.assertEquals('c:/documents and settings/user/file.pdf', uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_be_expanded_for_environment_variables(self):
+ uri = create_uri_from_file_path(self.path_var)
+ self.assertEquals('c:/documents and settings/user/file.pdf', uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_not_expand_short_names(self):
+ uri = create_uri_from_file_path(self.path_short)
+ self.assertEquals('c:/docum~1', uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_accept_unicode(self):
+ uri = create_uri_from_file_path(self.path_abs)
+ self.assertEquals(
+ u'file:///c:/documents and settings/aée/my documents/file.pdf',
+ uri.uri)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_have_correct_original_path(self):
+ uri = create_uri_from_file_path(self.path_rel)
+ self.assertEquals('sub/file.pdf', uri.original_path)
class TestUri_GivenInvalidUri(unittest.TestCase):
@@ -112,6 +325,39 @@
self.assertRaises(ValueError, Uri, 'http://')
+class TestLocalFileUri_GivenInvalidFileUri(unittest.TestCase):
+
+ @patch('os.path', _fake_os_path_unix())
+ def test_empty_should_raise_valueerror(self):
+ self.assertRaises(ValueError, LocalFileUri, '')
+
+ @patch('os.path', _fake_os_path_unix())
+ def test_no_scheme_should_raise_valueerror(self):
+ self.assertRaises(ValueError, LocalFileUri, '/path')
+
+ @patch('os.path', _fake_os_path_unix())
+ def test_no_path_should_raise_valueerror(self):
+ self.assertRaises(ValueError, LocalFileUri, 'file:')
+ self.assertRaises(ValueError, LocalFileUri, 'file://')
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_a_netloc_should_raise_valueerror(self):
+ self.assertRaises(ValueError, LocalFileUri,
+ 'file://localhost/c:/file.pdf')
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_incorrect_original_path_should_raise_valueerror_windows(self):
+ self.assertRaises(ValueError, LocalFileUri,
+ 'file://c:/documents and settings/file.pdf',
+ 'not_exists.pdf')
+
+ @patch('os.path', _fake_os_path_unix())
+ def test_incorrect_original_path_should_raise_valueerror_unix(self):
+ self.assertRaises(ValueError, LocalFileUri,
+ 'file:///root/file.pdf',
+ 'not_exists.pdf')
+
+
class TestUri_GivenAsciiUri(unittest.TestCase):
def setUp(self):
@@ -173,57 +419,82 @@
self.assertTrue(isinstance(unicode(self.uri), unicode))
-class TestUri_GivenFileScheme(unittest.TestCase):
+class TestLocalFileUri_GivenUnixStylePath(unittest.TestCase):
def setUp(self):
self.uri_unicode = u"file:///path/to/résource?a=é"
- self.uri = Uri(self.uri_unicode)
-
-
+ self.uri = LocalFileUri(self.uri_unicode)
+
+
+ @patch('os.path', _fake_os_path_unix())
def should_have_file_as_scheme(self):
self.assertEquals("file", self.uri.scheme)
+ @patch('os.path', _fake_os_path_unix())
def should_have_no_netloc(self):
self.assertEquals("", self.uri.netloc)
+ @patch('os.path', _fake_os_path_unix())
def should_have_no_query(self):
self.assertEquals("", self.uri.query)
+
+
+ @patch('os.path', _fake_os_path_unix())
+ def should_have_a_slash_at_first_position_in_path(self):
+ self.assertEquals(u"/path/to/résource?a=é", self.uri.path)
+
+
+ @patch('os.path', _fake_os_path_unix())
+ def should_have_same_original_path_as_path(self):
+ self.assertEquals(self.uri.path, self.uri.original_path)
+
+
+ @patch('os.path', _fake_os_path_unix())
+ def should_have_the_same_full_uri(self):
+ self.assertEquals(self.uri_unicode, self.uri.uri)
#pylint: disable-msg=C0103,R0904
-class TestUri_GivenFileSchemeWithNoHeadSlashAndWithBackslash(unittest.TestCase):
+class TestLocalFileUri_GivenWindowsStylePath(unittest.TestCase):
def setUp(self):
- self.uri_unicode = u"file://c:/a é\a.pdf"
- self.uri = Uri(self.uri_unicode)
-
-
+ self.uri_unicode = u"file:///c:/a é/a.pdf"
+ self.uri = LocalFileUri(self.uri_unicode)
+
+
+ @patch('os.path', _fake_os_path_windows())
def should_have_file_as_scheme(self):
self.assertEquals("file", self.uri.scheme)
+ @patch('os.path', _fake_os_path_windows())
def should_have_no_netloc(self):
self.assertEquals("", self.uri.netloc)
- def should_all_is_in_the_path(self):
- self.assertEquals(u"c:/a é\a.pdf", self.uri.path)
-
-
- def should_not_nomalised_path(self):
- self.assertEquals(u"c:/a é\a.pdf", self.uri.path)
-
-
+ @patch('os.path', _fake_os_path_windows())
+ def should_have_path_without_first_slash(self):
+ self.assertEquals(u"c:/a é/a.pdf", self.uri.path)
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def should_have_same_original_path_as_path(self):
+ self.assertEquals(self.uri.path, self.uri.original_path)
+
+
+ @patch('os.path', _fake_os_path_windows())
def should_have_no_query(self):
self.assertEquals("", self.uri.query)
+ @patch('os.path', _fake_os_path_windows())
def should_have_the_same_full_uri(self):
self.assertEquals(self.uri_unicode, self.uri.uri)
+ @patch('os.path', _fake_os_path_windows())
def test_unicode_property_should_have_the_full_uri(self):
self.assertEquals(self.uri_unicode, unicode(self.uri))
@@ -266,27 +537,42 @@
self.assertTrue(self.uri_base != self.uri_diff_all)
-class TestUriComparisons_GivenFileScheme(unittest.TestCase):
-
- def setUp(self):
- self.uri_unix_base = Uri(u"file:///path/a é/fé.pdf")
- self.uri_unix_diff_path = Uri(u"file:///path/a e/fé.pdf")
- self.uri_win_base = Uri(u"file://c:\path\a é\fé.pdf")
- self.uri_win_diff_path = Uri(u"file://c:\path\a é\fe.pdf")
- self.uri_win_base_norm = Uri(u"file://c:/path/a é/fé.pdf")
-
-
- def test_normalisation_should_be_ignored(self):
- self.assertTrue(self.uri_win_base != self.uri_win_base_norm)
-
-
+class TestLocalFileUriComparisons_GivenUnixStylePaths(unittest.TestCase):
+
+ def setUp(self):
+ self.uri_unix_base = LocalFileUri(u"file:///path/a é/fé.pdf")
+ self.uri_unix_diff_path = LocalFileUri(u"file:///path/a e/fé.pdf")
+
+
+ @patch('os.path', _fake_os_path_unix())
+ def test_same_instances_should_be_equal(self):
+ self.assertTrue(self.uri_unix_base == self.uri_unix_base)
+
+
+ @patch('os.path', _fake_os_path_unix())
+ def test_one_part_diff_should_not_be_equal(self):
+ self.assertTrue(self.uri_unix_base != self.uri_unix_diff_path)
+
+
+class TestLocalFileUriComparisons_GivenWindowsStylePaths(unittest.TestCase):
+
+ def setUp(self):
+ self.uri_win_base = LocalFileUri(u"file:///c:/path/a é/fé.pdf")
+ self.uri_win_diff_path = LocalFileUri(u"file:///c:/path/a é/fe.pdf")
+ self.uri_win_short = LocalFileUri(u"file:///c:/docum~1")
+ self.uri_win_long = LocalFileUri(u"file:///c:/documents and settings")
+
+
+ @patch('os.path', _fake_os_path_windows())
def test_same_instances_should_be_equal(self):
self.assertTrue(self.uri_win_base == self.uri_win_base)
- self.assertTrue(self.uri_unix_base == self.uri_unix_base)
-
-
+
+
+ @patch('os.path', _fake_os_path_windows())
def test_one_part_diff_should_not_be_equal(self):
self.assertTrue(self.uri_win_base != self.uri_win_diff_path)
- self.assertTrue(self.uri_unix_base != self.uri_unix_diff_path)
-
-
\ No newline at end of file
+
+
+ @patch('os.path', _fake_os_path_windows())
+ def test_does_not_handle_short_names(self):
+ self.assertTrue(self.uri_win_short != self.uri_win_long)
=== modified file 'slidepresenterview/utils/uri.py'
--- slidepresenterview/utils/uri.py 2010-05-17 04:15:53 +0000
+++ slidepresenterview/utils/uri.py 2010-06-02 05:43:25 +0000
@@ -4,6 +4,7 @@
# https://launchpad.net/slidepresenterview
#
# Copyright (C) 2010 Felix-Antoine Bourbonnais <bouf10pub _AT@. rubico.info>
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,21 +26,67 @@
import os
from urllib2 import urlparse
-__all__ = ['Uri', 'file_path_to_uri']
-
-
-def file_path_to_uri(file_path):
- """
- Converts a file path into a full Uri.
+__all__ = ['create_uri_from_uri_string', 'create_uri_from_file_path',
+ 'Uri', 'LocalFileUri']
+
+
+def create_uri_from_uri_string(uri_string):
+ """
+ Factory to create the right Uri type for a given uri_string.
+ """
+ uri_split = urlparse.urlsplit(uri_string, allow_fragments=False)
+
+ if uri_split.scheme == 'file' and uri_split.netloc == '':
+ return LocalFileUri(uri_string)
+
+ return Uri(uri_string)
+
+
+def create_uri_from_file_path(file_path):
+ """
+ Converts a file path into a full LocalFileUri.
+
+ Paths are expanded for environment variables.
If the path is relative, it will be extended to the corresponding
absolute location.
- Paths are normalised.
+ Paths are normalized and case normalized.
+
+ Windows-specific behavior
+ -------------------------
+
+ Backslashes are converted to slashes.
+
+ Short names (old DOS-style) are not expanded to their long name
+ correspondence.
+ For example, 'C:\\PROGRA~1' is not expanded to 'C:\\Program Files'.
"""
+ file_path_norm = _normalize_file_path(file_path)
+ file_path_uri = _normalize_windows_specific_uri(file_path_norm)
+ return LocalFileUri("file://%s" % file_path_uri, file_path)
+
+
+def _normalize_file_path(file_path):
+ file_path = os.path.expanduser(file_path)
+ file_path = os.path.expandvars(file_path)
file_path = os.path.abspath(file_path)
file_path = os.path.normpath(file_path)
- return Uri("file://%s" % file_path)
+ file_path = os.path.normcase(file_path)
+ return file_path
+
+
+def _normalize_windows_specific_uri(file_path):
+ file_path = file_path.replace('\\', '/')
+ if _is_windows_style_absolute_path(file_path):
+ file_path = "/" + file_path
+
+ return file_path
+
+
+def _is_windows_style_absolute_path(file_path):
+ drive, _ = os.path.splitdrive(file_path)
+ return drive != ""
class Uri(object):
@@ -82,59 +129,173 @@
"""
@raise ValueError: Invalid (malformed) uri string.
"""
- if len(uri_string) == 0:
- raise ValueError("Invalid (empty) URI.")
-
- uri_split = urlparse.urlsplit(uri_string, allow_fragments=False)
-
- self.scheme = uri_split.scheme
- if self.scheme == 'file':
- self.netloc = ''
- self.path = uri_split.netloc + uri_split.path
- else:
- self.netloc = uri_split.netloc
- self.path = uri_split.path
- self.query = uri_split.query
+ self._decompose_uri(uri_string)
if len(self.scheme) == 0:
raise ValueError("Invalid URI: scheme is mandatory.")
if len(self.path) == 0:
raise ValueError("Invalid URI: path is mandatory.")
-
-
+
+
+ def _decompose_uri(self, uri_string):
+ uri_split = urlparse.urlsplit(uri_string, allow_fragments=False)
+
+ self.scheme = uri_split.scheme
+ self.netloc = uri_split.netloc
+ self.path = uri_split.path
+ self.query = uri_split.query
+
+
@property
def uri(self):
uri = "%s://%s%s" % (self.scheme, self.netloc, self.path)
if len(self.query) > 0:
uri = "%s?%s" % (uri, self.query)
return uri
-
+
def __str__(self):
return self.uri
def __cmp__(self, other_uri):
- return self._cmp_textual_uris(self.uri, other_uri.uri)
-
-
- def _cmp_textual_uris(self, uri_textual, other_uri_textual):
- import unicodedata, sys
-
- try:
- encoding = sys.stdout.encoding
- except: # Probably not executed by a human...
- encoding = 'UTF-8' # So assume UTF-8 since our .py are in utf8.
-
- if not isinstance(uri_textual, unicode):
- uri_unicode = unicode(uri_textual, encoding)
- else:
- uri_unicode = uri_textual
-
- if not isinstance(other_uri_textual, unicode):
- other_uri_unicode = unicode(other_uri_textual, encoding)
- else:
- other_uri_unicode = other_uri_textual
-
- return cmp( unicodedata.normalize('NFC', uri_unicode),
- unicodedata.normalize('NFC', other_uri_unicode) )
\ No newline at end of file
+ return self._cmp_textual_strings(self.uri, other_uri.uri)
+
+
+ def _cmp_textual_strings(self, string, other_string):
+ import unicodedata
+
+ string_unicode = self._ensure_string_is_in_unicode(string)
+ other_string_unicode = self._ensure_string_is_in_unicode(other_string)
+
+ return cmp( unicodedata.normalize('NFC', string_unicode),
+ unicodedata.normalize('NFC', other_string_unicode) )
+
+
+ def _ensure_string_is_in_unicode(self, string):
+ if isinstance(string, unicode):
+ return string
+ else:
+ encoding = self._get_encoding()
+ return unicode(string, encoding)
+
+
+ def _get_encoding(self):
+ import sys
+
+ try:
+ return sys.stdout.encoding
+ except: # Probably not executed by a human...
+ return 'UTF-8' # So assume UTF-8 since our .py are in utf8.
+
+
+class LocalFileUri(Uri):
+ """
+ Represents a specialization of an Uri for a local file.
+
+ We are really picky with the syntax of Uris for the local files.
+ Each path of a Uri on a local file should be absolute, expansed and
+ case normalized. It is easy to create a LocalFileUri from any local
+ file path, just use the create_uri_from_file_path() function.
+
+ Others differences with general Uris are
+ ----------------------------------------
+
+ - LocalFileUri objects have an empty network location.
+ - The comparison between LocalFileUri objects is based on verifying if
+ the paths lead to the same file (if possible).
+ - LocalFileUri also have a property original_path that allows to keep
+ track of the original path that created an Uri. This path can be
+ relative, unexpanded and not case normalized.
+
+ Special considerations for Windows Uris
+ ---------------------------------------
+
+ We consider that the ONLY valid Uris are of the form:
+
+ file:///<drive_letter>:/path/to/the/file.<extension>
+
+ Note that
+ - three slashes must be used after 'file:';
+ - the normal colon (':') must be used after the drive letter (not '|');
+ - slashes must be used in the path (no backslashes).
+
+ However, to ease manipulation of path, the path property for Windows local
+ file will always remove the slash before the drive letter.
+
+ @ivar scheme: The protocol/scheme ('file' of course)
+ @ivar netloc: The network location (empty for local files)
+ @ivar path: The complete resource path (absolute, expanded and case
+ normalized)
+ @ivar original_path: The path used to create this Uri.
+ @ivar query: The query
+ @ivar uri: The full uri (unicode string)
+ """
+
+ def __init__(self, uri_string, original_path=None):
+ """
+ @param original_path: The original path of the file.
+ The property path will be used if original_path
+ is None.
+
+ @raise ValueError: Invalid (malformed) uri string.
+ """
+ Uri.__init__(self, uri_string)
+
+ if len(self.netloc) != 0:
+ raise ValueError("Invalid local file URI: cannot have network "
+ + "location.")
+
+ if original_path is None:
+ original_path = self.path
+ else:
+ if self._cmp_file_paths(self.path, original_path) != 0:
+ raise ValueError("Invalid local file URI: original path is "
+ + "invalid.")
+
+ self.original_path = original_path
+
+
+ @property
+ def path(self):
+ if self._path != "":
+ path_without_first_slash = self._path[1:]
+ if _is_windows_style_absolute_path(path_without_first_slash):
+ return path_without_first_slash
+
+ return self._path
+
+
+ @path.setter
+ def path(self, value):
+ self._path = value
+
+
+ @property
+ def uri(self):
+ """
+ Override the uri property to make sure to use the correct path.
+ """
+ uri = "%s://%s%s" % (self.scheme, self.netloc, self._path)
+ if len(self.query) > 0:
+ uri = "%s?%s" % (uri, self.query)
+ return uri
+
+
+ def __cmp__(self, other_uri):
+ return self._cmp_file_paths(self.path, other_uri.path)
+
+
+ def _cmp_file_paths(self, path, other_path):
+ try:
+ is_same_file = os.path.samefile(path, other_path)
+ if is_same_file:
+ return 0
+ else:
+ return cmp(path, other_path)
+ except AttributeError: # We do our best to make a comparison.
+ path = _normalize_file_path(path)
+ path = _normalize_windows_specific_uri(path)
+ other_path = _normalize_file_path(other_path)
+ other_path = _normalize_windows_specific_uri(other_path)
+ return self._cmp_textual_strings(path, other_path)
Follow ups