diff --git twisted/python/test/test_urlpath.py twisted/python/test/test_urlpath.py index 4919cdc..fb747dd 100644 --- twisted/python/test/test_urlpath.py +++ twisted/python/test/test_urlpath.py @@ -1,44 +1,113 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. + """ Tests for L{twisted.python.urlpath}. """ from twisted.trial import unittest -from twisted.python import urlpath +from twisted.python.urlpath import URLPath + class URLPathTestCase(unittest.TestCase): + + def setUp(self): - self.path = urlpath.URLPath.fromString("http://example.com/foo/bar?yes=no&no=yes#footer") + url = "http://example.com/foo/bar?yes=no&no=yes#footer" + self.path = URLPath.fromString(url) + - def testStringConversion(self): - self.assertEqual(str(self.path), "http://example.com/foo/bar?yes=no&no=yes#footer") + def test_stringConversion(self): + self.assertEqual(str(self.path), + "http://example.com/foo/bar?yes=no&no=yes#footer") - def testChildString(self): - self.assertEqual(str(self.path.child('hello')), "http://example.com/foo/bar/hello") - self.assertEqual(str(self.path.child('hello').child('')), "http://example.com/foo/bar/hello/") - def testSiblingString(self): - self.assertEqual(str(self.path.sibling('baz')), 'http://example.com/foo/baz') + def test_childString(self): + self.assertEqual(str(self.path.child('hello')), + "http://example.com/foo/bar/hello") + self.assertEqual(str(self.path.child('hello').child('')), + "http://example.com/foo/bar/hello/") - # The sibling of http://example.com/foo/bar/ - # is http://example.comf/foo/bar/baz - # because really we are constructing a sibling of - # http://example.com/foo/bar/index.html - self.assertEqual(str(self.path.child('').sibling('baz')), 'http://example.com/foo/bar/baz') - def testParentString(self): - # parent should be equivalent to '..' - # 'foo' is the current directory, '/' is the parent directory + def test_siblingString(self): + """ + The sibling of http://example.com/foo/bar/ + is http://example.comf/foo/bar/baz + because really we are constructing a sibling of + http://example.com/foo/bar/index.html + """ + self.assertEqual(str(self.path.sibling('baz')), + 'http://example.com/foo/baz') + self.assertEqual(str(self.path.child('').sibling('baz')), + 'http://example.com/foo/bar/baz') + + + def test_parentString(self): + """ + parent should be equivalent to '..' + 'foo' is the current directory, '/' is the parent directory + """ self.assertEqual(str(self.path.parent()), 'http://example.com/') - self.assertEqual(str(self.path.child('').parent()), 'http://example.com/foo/') - self.assertEqual(str(self.path.child('baz').parent()), 'http://example.com/foo/') - self.assertEqual(str(self.path.parent().parent().parent().parent().parent()), 'http://example.com/') + self.assertEqual(str(self.path.child('').parent()), + 'http://example.com/foo/') + self.assertEqual(str(self.path.child('baz').parent()), + 'http://example.com/foo/') + self.assertEqual(str(self.path.parent().parent().parent() + .parent().parent()), 'http://example.com/') + - def testHereString(self): - # here should be equivalent to '.' + def test_hereString(self): + """ + here should be equivalent to '.' + """ self.assertEqual(str(self.path.here()), 'http://example.com/foo/') - self.assertEqual(str(self.path.child('').here()), 'http://example.com/foo/bar/') + self.assertEqual(str(self.path.child('').here()), + 'http://example.com/foo/bar/') + + + def test_clone(self): + """ + L{clone} should return a copy of the current path. + """ + self.assertEqual(str(self.path.clone()), + 'http://example.com/foo/bar?yes=no&no=yes#footer') + + + def test_cloneNoQuery(self): + """ + L{clone} with C{keepQuery=False} should return a clone + lacking the query parameters. + """ + self.assertEqual(str(self.path.clone(keepQuery=False)), + 'http://example.com/foo/bar#footer') + + + def test_cloneNoFragment(self): + """ + L{clone} with C{keepFragment=False} should return a + clone lacking the fragment. + """ + self.assertEqual(str(self.path.clone(keepFragment=False)), + 'http://example.com/foo/bar?yes=no&no=yes') + + + + def test_up(self): + """ + L{up} should return a new L{URLPath} with the last segment + removed and without the trailing slash. + """ + self.assertEqual(str(self.path.child('foo').up()), + str(self.path.clone(keepQuery=False, + keepFragment=False)), + "child(...).up() should be identical to the original " + "path but without the query or fragment") + self.assertEqual(str(self.path.up().up().up().up()), + 'http://example.com/') + + path = URLPath.fromString('http://example.com/foo/') + self.assertEqual(str(path.up()), 'http://example.com/foo') + diff --git twisted/python/urlpath.py twisted/python/urlpath.py index 1c15f09..5b6c15f 100644 --- twisted/python/urlpath.py +++ twisted/python/urlpath.py @@ -7,7 +7,11 @@ import urlparse import urllib + + class URLPath: + + def __init__(self, scheme='', netloc='localhost', path='', query='', fragment=''): self.scheme = scheme or 'http' @@ -18,6 +22,7 @@ class URLPath: _qpathlist = None _uqpathlist = None + def pathList(self, unquote=0, copy=1): if self._qpathlist is None: @@ -32,6 +37,7 @@ class URLPath: else: return result + def fromString(klass, st): t = urlparse.urlsplit(st) u = klass(*t) @@ -39,26 +45,51 @@ class URLPath: fromString = classmethod(fromString) + def fromRequest(klass, request): return klass.fromString(request.prePathURL()) fromRequest = classmethod(fromRequest) - def _pathMod(self, newpathsegs, keepQuery): + + def _pathMod(self, newpathsegs, keepQuery, keepFragment=False): + """ + Create a new L{URLPath} from a list of path segments and optionally + this instance's query and fragment. + + @param newpathsegs: A list of path segment strings to use in creating + the new L{URLPath} object. + @type newpathsegs: iterable + + @param keepQuery: Flag indicating that the query portion of this + L{URLPath} should be included in the new instance. + @type keepQuery: bool + + @param keepFragment: Flag indicating that the fragment portion of this + L{URLPath} sohuld be included in the new instance. + @type keepFragment: bool + """ if keepQuery: query = self.query else: query = '' + if keepFragment: + fragment = self.fragment + else: + fragment = '' return URLPath(self.scheme, self.netloc, '/'.join(newpathsegs), - query) + query, + fragment) + def sibling(self, path, keepQuery=0): l = self.pathList() l[-1] = path return self._pathMod(l, keepQuery) + def child(self, path, keepQuery=0): l = self.pathList() if l[-1] == '': @@ -67,6 +98,7 @@ class URLPath: l.append(path) return self._pathMod(l, keepQuery) + def parent(self, keepQuery=0): l = self.pathList() if l[-1] == '': @@ -78,12 +110,45 @@ class URLPath: l[-1] = '' return self._pathMod(l, keepQuery) + + def clone(self, keepQuery=True, keepFragment=True): + """ + Get a copy of this path (including the query and fragment by default). + + C{URLPath('http://example.com/foo/bar?hey=ho').clone(False)} is + equivalent to C{URLPath('http://example.com/foo/bar')}. + + @param keepQuery: If C{False} then don't include the query parameters. + @param keepFragment: If C{False} then don't include the fragment. + + @return: A L{URLPath} identical to me (but without the query portion + depending on C{keepQuery}) + """ + return self._pathMod(self.pathList(), keepQuery, keepFragment) + + + def up(self, keepQuery=0): + """ + Inverse of L{child}. This differs from L{parent} in that this will not + return a path with a trailing slash. + + For instance, the path "up" from C{http://example.com/foo/bar} is + C{http://example.com/foo}. + + @return: A new L{URLPath} one segment up from this path. + """ + l = self.pathList() + del l[-1] + return self._pathMod(l, keepQuery) + + def here(self, keepQuery=0): l = self.pathList() if l[-1] != '': l[-1] = '' return self._pathMod(l, keepQuery) + def click(self, st): """Return a path which is the URL where a browser would presumably take you if you clicked on a link with an HREF as given. @@ -108,7 +173,6 @@ class URLPath: query, fragment) - def __str__(self): x = urlparse.urlunsplit(( @@ -116,6 +180,7 @@ class URLPath: self.query, self.fragment)) return x + def __repr__(self): return ('URLPath(scheme=%r, netloc=%r, path=%r, query=%r, fragment=%r)' % (self.scheme, self.netloc, self.path, self.query, self.fragment)) diff --git twisted/topfiles/6673.feature twisted/topfiles/6673.feature new file mode 100644 index 0000000..0f10f40 --- /dev/null +++ twisted/topfiles/6673.feature @@ -0,0 +1 @@ +twisted.python.urlpath.URLPath has two new methods: clone for creating a copy of the path (with or without query/fragment) and up, an inverse of child. \ No newline at end of file