root/livinglogic.python.xist/src/ll/url.py @ 4042:374af534512f

Revision 4042:374af534512f, 77.6 KB (checked in by Walter Doerwald <walter@…>, 9 years ago)

Strip trailing whitespace.

Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4## Copyright 1999-2010 by LivingLogic AG, Bayreuth/Germany.
5## Copyright 1999-2010 by Walter Dörwald
6##
7## All Rights Reserved
8##
9## See ll/__init__.py for the license
10
11
12"""
13:mod:`ll.url` contains an :rfc:`2396` compliant implementation of URLs and
14classes for accessing resource metadata as well as file like classes for
15reading and writing resource data.
16
17These three levels of functionality are implemented in three classes:
18
19    :class:`URL`
20        :class:`URL` objects are the names of resources and can be used and
21        modified, regardless of the fact whether these resources actually exits.
22        :class:`URL` objects never hits the hard drive or the net.
23
24    :class:`Connection`
25        :class:`Connection` objects contain functionality that accesses and
26        changes file metadata (like last modified date, permission bits,
27        directory structure etc.). A connection object can be created by calling
28        the :meth:`connect` method on a :class:`URL` object.
29
30    :class:`Resource`
31        :class:`Resource` objects are file like objects that work with the actual
32        bytes that make up the file data. This functionality lives in the
33        :class:`Resource` class and its subclasses. Creating a resource is done
34        by calling the :meth:`open` method on a :class:`Connection` or a
35        :class:`URL`.
36"""
37
38
39import sys, os, urllib, urllib2, types, mimetypes, mimetools, cStringIO, warnings
40import datetime, cgi, fnmatch, cPickle, errno, threading
41
42try:
43    from email import utils as emutils
44except ImportError:
45    from email import Utils as emutils
46
47# don't fail when :mod:`pwd` or :mod:`grp` can't be imported, because if this
48# doesn't work, we're probably on Windows and :func:`os.chown` won't work anyway.
49try:
50    import pwd, grp
51except ImportError:
52    pass
53
54try:
55    import execnet
56except ImportError:
57    execnet = None
58
59try:
60    import Image
61except ImportError:
62    pass
63
64try:
65    import astyle
66except ImportError:
67    from ll import astyle
68
69from ll import misc
70
71
72__docformat__ = "reStructuredText"
73
74
75os.stat_float_times(True)
76
77
78def mime2dt(s):
79    return datetime.datetime(*emutils.parsedate(s)[:7])
80
81
82weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
83monthname = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
84
85
86def httpdate(dt):
87    """
88    Return a string suitable for a "Last-Modified" and "Expires" header.
89
90    :var:`dt` is a :class:`datetime.datetime` object in UTC.
91    """
92    return "{1}, {0.day:02d} {2:3} {0.year:4} {0.hour:02}:{0.minute:02}:{0.second:02} GMT".format(dt, weekdayname[dt.weekday()], monthname[dt.month])
93
94
95try:
96    from _url import escape as _escape, unescape as _unescape, normalizepath as _normalizepath
97except ImportError:
98    def _normalizepath(path_segments):
99        new_path_segments = []
100        l = len(path_segments)
101        for i in xrange(l):
102            segment = path_segments[i]
103            if not segment:
104                if i==l-1:
105                    new_path_segments.append("")
106            elif segment==".." and len(new_path_segments) and new_path_segments[-1] != "..":
107                new_path_segments.pop()
108                if i==l-1:
109                    new_path_segments.append("")
110            else:
111                new_path_segments.append(segment)
112        return new_path_segments
113
114    def _escape(s, safe=""):
115        if not safe:
116            safe = "".join(chr(c) for c in xrange(128))
117        return urllib.quote_plus(s.encode("utf-8"), safe)
118
119    def _unescape(s):
120        s = urllib.unquote_plus(s)
121        try:
122            return s.decode("utf-8")
123        except UnicodeError:
124            return s.decode("latin-1")
125
126
127alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
128alphanum = alpha + "0123456789"
129mark = "-_.!~*'()"
130additionalsafe = "[]"
131safe = alphanum + mark + additionalsafe
132pathsafe = safe + ":@&=+$,;" + "|" # add "|" for Windows paths
133querysafe = alphanum
134fragsafe = alphanum
135
136schemecharfirst = alpha
137schemechar = alphanum + "+-."
138
139
140def _urlencode(query_parts):
141    if query_parts is not None:
142        res = []
143        items = query_parts.items()
144        # generate a canonical order for the names
145        items.sort()
146        for (name, values) in items:
147            if not isinstance(values, (list, tuple)):
148                values = (values,)
149            else:
150                # generate a canonical order for the values
151                values.sort()
152            for value in values:
153                res.append("{0}={1}".format(_escape(name, querysafe), _escape(value, querysafe)))
154        return "&".join(res)
155    else:
156        return None
157
158
159class Context(object):
160    """
161    Calling :meth:`URL.open` or :meth:`URL.connect` :class:`Connection` object.
162    To avoid constantly creating new connections you can pass a :class:`Context`
163    object to those methods. Connections will be stored in the :class:`Context`
164    object and will be reused by those methods.
165
166    A :class:`Context` object can also be used as a context manager
167    (see :pep:`346` for more info). This context object will be used for all
168    :meth:`open` and :meth:`connect` calls inside the ``with`` block. (Note that
169    after the end of the ``with`` block, all connections will be closed.)
170    """
171    def __init__(self):
172        self.schemes = {}
173
174    def closeall(self):
175        """
176        Close and drop all connections in this context.
177        """
178        for scheme in self.schemes:
179            schemereg[scheme].closeall(self)
180        self.schemes = {}
181
182    def __enter__(self):
183        self.prev = threadlocalcontext.context
184        threadlocalcontext.context = self
185
186    def __exit__(self, type, value, traceback):
187        threadlocalcontext.context = self.prev
188        del self.prev
189        self.closeall()
190
191
192class ThreadLocalContext(threading.local):
193    context = Context()
194
195threadlocalcontext = ThreadLocalContext()
196
197
198def getcontext(context):
199    if context is None:
200        return threadlocalcontext.context
201    return context
202
203
204class Connection(object):
205    """
206    A :class:`Connection` object is used for accessing and modifying the
207    metadata associated with a file. It it created by calling the
208    :meth:`connect` method on a :class:`URL` object.
209    """
210
211    @misc.notimplemented
212    def stat(self, url):
213        """
214        Return the result of a :func:`stat` call on the file :var:`url`.
215        """
216
217    @misc.notimplemented
218    def lstat(self, url):
219        """
220        Return the result of a :func:`stat` call on the file :var:`url`. Like
221        :meth:`stat`, but does not follow symbolic links.
222        """
223
224    @misc.notimplemented
225    def chmod(self, url, mode):
226        """
227        Set the access mode of the file :var:`url` to :var:`mode`.
228        """
229
230    @misc.notimplemented
231    def chown(self, url, owner=None, group=None):
232        """
233        Change the owner and/or group of the file :var:`url`.
234        """
235
236    @misc.notimplemented
237    def lchown(self, url, owner=None, group=None):
238        """
239        Change the owner and/or group of the file :var:`url`
240        (ignoring symbolic links).
241        """
242
243    @misc.notimplemented
244    def uid(self, url):
245        """
246        Return the user id of the owner of the file :var:`url`.
247        """
248
249    @misc.notimplemented
250    def gid(self, url):
251        """
252        Return the group id the file :var:`url` belongs to.
253        """
254
255    @misc.notimplemented
256    def owner(self, url):
257        """
258        Return the name of the owner of the file :var:`url`.
259        """
260
261    @misc.notimplemented
262    def group(self, url):
263        """
264        Return the name of the group the file :var:`url` belongs to.
265        """
266
267    def mimetype(self, url):
268        """
269        Return the mimetype of the file :var:`url`.
270        """
271        name = self._url2filename(url)
272        mimetype = mimetypes.guess_type(name)[0]
273        return mimetype or "application/octet-stream"
274
275    @misc.notimplemented
276    def exists(self, url):
277        """
278        Test whether the file :var:`url` exists.
279        """
280
281    @misc.notimplemented
282    def isfile(self, url):
283        """
284        Test whether the resource :var:`url` is a file.
285        """
286
287    @misc.notimplemented
288    def isdir(self, url):
289        """
290        Test whether the resource :var:`url` is a directory.
291        """
292
293    @misc.notimplemented
294    def islink(self, url):
295        """
296        Test whether the resource :var:`url` is a link.
297        """
298
299    @misc.notimplemented
300    def ismount(self, url):
301        """
302        Test whether the resource :var:`url` is a mount point.
303        """
304
305    @misc.notimplemented
306    def access(self, url, mode):
307        """
308        Test for access to the file/resource :var:`url`.
309        """
310
311    def size(self, url):
312        """
313        Return the size of the file :var:`url`.
314        """
315        return self.stat(url).st_size
316
317    def imagesize(self, url):
318        """
319        Return the size of the image :var:`url` (if the resource is an image file)
320        as a ``(width, height)`` tuple. This requires the PIL__.
321
322        __ http://www.pythonware.com/products/pil/
323        """
324        stream = self.open(url, "rb")
325        img = Image.open(stream) # Requires PIL
326        imagesize = img.size
327        stream.close()
328        return imagesize
329
330    def cdate(self, url):
331        """
332        Return the "metadate change" date of the file/resource :var:`url`
333        as a :class:`datetime.datetime` object in UTC.
334        """
335        return datetime.datetime.utcfromtimestamp(self.stat(url).st_ctime)
336
337    def adate(self, url):
338        """
339        Return the last access date of the file/resource :var:`url` as a
340        :class:`datetime.datetime` object in UTC.
341        """
342        return datetime.datetime.utcfromtimestamp(self.stat(url).st_atime)
343
344    def mdate(self, url):
345        """
346        Return the last modification date of the file/resource :var:`url`
347        as a :class:`datetime.datetime` object in UTC.
348        """
349        return datetime.datetime.utcfromtimestamp(self.stat(url).st_mtime)
350
351    def resheaders(self, url):
352        """
353        Return the MIME headers for the file/resource :var:`url`.
354        """
355        return mimetools.Message(cStringIO.StringIO("Content-Type: {0}\nContent-Length: {1}\nLast-modified: {2}\n".format(self.mimetype(url), self.size(url), httpdate(self.mdate(url)))))
356
357    @misc.notimplemented
358    def remove(self, url):
359        """
360        Remove the file :var:`url`.
361        """
362
363    @misc.notimplemented
364    def rmdir(self, url):
365        """
366        Remove the directory :var:`url`.
367        """
368
369    @misc.notimplemented
370    def rename(self, url, target):
371        """
372        Renames :var:`url` to :var:`target`. This might not work if :var:`target`
373        has a different scheme than :var:`url` (or is on a different server).
374        """
375
376    @misc.notimplemented
377    def link(self, url, target):
378        """
379        Create a hard link from :var:`url` to :var:`target`. This will not work
380        if :var:`target` has a different scheme than :var:`url` (or is on a
381        different server).
382        """
383
384    @misc.notimplemented
385    def symlink(self, url, target):
386        """
387        Create a symbolic link from :var:`url` to :var:`target`. This will not
388        work if :var:`target` has a different scheme than :var:`url` (or is on a
389        different server).
390        """
391
392    @misc.notimplemented
393    def chdir(self, url):
394        """
395        Change the current directory to :var:`url`.
396        """
397        os.chdir(self.name)
398
399    @misc.notimplemented
400    def mkdir(self, url, mode=0777):
401        """
402        Create the directory :var:`url`.
403        """
404
405    @misc.notimplemented
406    def makedirs(self, url, mode=0777):
407        """
408        Create the directory :var:`url` and all intermediate ones.
409        """
410
411    @misc.notimplemented
412    def listdir(self, url, pattern=None):
413        """
414        Return a list of items in the directory :var:`url`. The elements of the
415        list are :class:`URL` objects relative to :var:`url`. With the optional
416        :var:`pattern` argument, this only lists items whose names match the
417        given pattern.
418        """
419
420    @misc.notimplemented
421    def files(self, url, pattern=None):
422        """
423        Return a list of files in the directory :var:`url`. The elements of the
424        list are :class:`URL` objects relative to :var:`url`. With the optional
425        :var:`pattern` argument, this only lists items whose names match the
426        given pattern.
427        """
428
429    @misc.notimplemented
430    def dirs(self, url, pattern=None):
431        """
432        Return a list of directories in the directory :var:`url`. The elements
433        of the list are :class:`URL` objects relative to :var:`url`. With the
434        optional :var:`pattern` argument, this only lists items whose names match
435        the given pattern.
436        """
437
438    @misc.notimplemented
439    def walk(self, url, pattern=None):
440        """
441        Return a recursive iterator over files and subdirectories. The iterator
442        yields :class:`URL` objects naming each child URL of the directory
443        :var:`url` and its descendants relative to :var:`url`. This performs
444        a depth-first traversal, returning each directory before all its children.
445        With the optional :var:`pattern` argument, only yield items whose
446        names match the given pattern.
447        """
448
449    @misc.notimplemented
450    def walkfiles(self, url, pattern=None):
451        """
452        Return a recursive iterator over files in the directory :var:`url`. With
453        the optional :var:`pattern` argument, only yield files whose names match
454        the given pattern.
455        """
456
457    @misc.notimplemented
458    def walkdirs(self, url, pattern=None):
459        """
460        Return a recursive iterator over subdirectories in the directory
461        :var:`url`. With the optional :var:`pattern` argument, only yield
462        directories whose names match the given pattern.
463        """
464
465    @misc.notimplemented
466    def open(self, url, *args, **kwargs):
467        """
468        Open :var:`url` for reading or writing. :meth:`open` returns a
469        :class:`Resource` object.
470
471        Which additional parameters are supported depends on the actual
472        resource created. Some common parameters are:
473
474            :var:`mode` : string
475                A string indicating how the file is to be opened (just like the
476                mode argument for the builtin :func:`open` (e.g. ``"rb"`` or
477                ``"wb"``).
478
479            :var:`headers` : mapping
480                Additional headers to use for an HTTP request.
481
482            :var:`data` : byte string
483                Request body to use for an HTTP POST request.
484
485            :var:`remotepython` : string or ``None``
486                Name of the Python interpreter to use on the remote side (used by
487                ``ssh`` URLs)
488
489            :var:`nice` : int or ``None``
490                Nice level for the remote python (used by ``ssh`` URLs)
491        """
492
493
494class LocalConnection(Connection):
495    def _url2filename(self, url):
496        return os.path.expanduser(url.local())
497
498    def stat(self, url):
499        return os.stat(self._url2filename(url))
500
501    def lstat(self, url):
502        return os.lstat(self._url2filename(url))
503
504    def chmod(self, url, mode):
505        name = self._url2filename(url)
506        os.chmod(name, mode)
507
508    def _chown(self, func, url, owner, group):
509        name = self._url2filename(url)
510        if owner is not None or group is not None:
511            if owner is None or group is None:
512                stat = os.stat(name)
513            if owner is None:
514                owner = stat.st_uid
515            elif isinstance(owner, basestring):
516                owner = pwd.getpwnam(owner)[2]
517            if group is None:
518                group = stat.st_gid
519            elif isinstance(group, basestring):
520                group = grp.getgrnam(group)[2]
521            func(name, owner, group)
522
523    def chown(self, url, owner=None, group=None):
524        self._chown(os.chown, url, owner, group)
525
526    def lchown(self, url, owner=None, group=None):
527        self._chown(os.lchown, url, owner, group)
528
529    def chdir(self, url):
530        os.chdir(self._url2filename(url))
531
532    def mkdir(self, url, mode=0777):
533        os.mkdir(self._url2filename(url), mode)
534
535    def makedirs(self, url, mode=0777):
536        os.makedirs(self._url2filename(url), mode)
537
538    def uid(self, url):
539        return self.stat(url).st_uid
540
541    def gid(self, url):
542        return self.stat(url).st_gid
543
544    def owner(self, url):
545        return pwd.getpwuid(self.uid(url))[0]
546
547    def group(self, url):
548        return grp.getgrgid(self.gid(url))[0]
549
550    def exists(self, url):
551        return os.path.exists(self._url2filename(url))
552
553    def isfile(self, url):
554        return os.path.isfile(self._url2filename(url))
555
556    def isdir(self, url):
557        return os.path.isdir(self._url2filename(url))
558
559    def islink(self, url):
560        return os.path.islink(self._url2filename(url))
561
562    def ismount(self, url):
563        return os.path.ismount(self._url2filename(url))
564
565    def access(self, url, mode):
566        return os.access(self._url2filename(url), mode)
567
568    def remove(self, url):
569        return os.remove(self._url2filename(url))
570
571    def rmdir(self, url):
572        return os.rmdir(self._url2filename(url))
573
574    def rename(self, url, target):
575        name = self._url2filename(url)
576        if not isinstance(target, URL):
577            target = URL(target)
578        targetname = self._url2filename(target)
579        os.rename(name, target)
580
581    def link(self, url, target):
582        name = self._url2filename(url)
583        if not isinstance(target, URL):
584            target = URL(target)
585        target = self._url2filename(target)
586        os.link(name, target)
587
588    def symlink(self, url, target):
589        name = self._url2filename(url)
590        if not isinstance(target, URL):
591            target = URL(target)
592        target = self._url2filename(target)
593        os.symlink(name, target)
594
595    def listdir(self, url, pattern=None):
596        name = self._url2filename(url)
597        result = []
598        for childname in os.listdir(name):
599            if pattern is None or fnmatch.fnmatch(childname, pattern):
600                if os.path.isdir(os.path.join(name, childname)):
601                    result.append(Dir(childname, scheme=url.scheme))
602                else:
603                    result.append(File(childname, scheme=url.scheme))
604        return result
605
606    def files(self, url, pattern=None):
607        name = self._url2filename(url)
608        result = []
609        for childname in os.listdir(name):
610            if pattern is None or fnmatch.fnmatch(childname, pattern):
611                if os.path.isfile(os.path.join(name, childname)):
612                    result.append(File(childname, scheme=url.scheme))
613        return result
614
615    def dirs(self, url, pattern=None):
616        name = self._url2filename(url)
617        result = []
618        for childname in os.listdir(name):
619            if pattern is None or fnmatch.fnmatch(childname, pattern):
620                if os.path.isdir(os.path.join(name, childname)):
621                    result.append(Dir(childname, scheme=url.scheme))
622        return result
623
624    def _walk(self, base, name, pattern, which):
625        if name:
626            fullname = os.path.join(base, name)
627        else:
628            fullname = base
629        for childname in os.listdir(fullname):
630            ful4childname = os.path.join(fullname, childname)
631            relchildname = os.path.join(name, childname)
632            isdir = os.path.isdir(ful4childname)
633            if (pattern is None or fnmatch.fnmatch(childname, pattern)) and which[isdir]:
634                url = urllib.pathname2url(relchildname)
635                if isdir:
636                    url += "/"
637                yield URL(url)
638            if isdir:
639                for subchild in self._walk(base, relchildname, pattern, which):
640                    yield subchild
641
642    def walk(self, url, pattern=None):
643        return self._walk(self._url2filename(url), "", pattern, (True, True))
644
645    def walkfiles(self, url, pattern=None):
646        return self._walk(self._url2filename(url), "", pattern, (True, False))
647
648    def walkdirs(self, url, pattern=None):
649        return self._walk(self._url2filename(url), "", pattern, (False, True))
650
651    def open(self, url, mode="rb"):
652        return FileResource(url, mode)
653
654
655class SshConnection(Connection):
656    remote_code = """
657        import os, urllib, cPickle, fnmatch
658
659        os.stat_float_times(True)
660        files = {}
661        iterators = {}
662
663        def ownergroup(filename, owner=None, group=None):
664            if owner is not None or group is not None:
665                if owner is None or group is None:
666                    if isinstance(filename, basestring):
667                        stat = os.stat(filename)
668                    else:
669                        stat = os.fstat(files[filename].fileno())
670                if owner is None:
671                    owner = stat.st_uid
672                elif isinstance(owner, basestring):
673                    import pwd
674                    owner = pwd.getpwnam(owner)[2]
675
676                if group is None:
677                    group = stat.st_gid
678                elif isinstance(group, basestring):
679                    import grp
680                    group = grp.getgrnam(group)[2]
681            return (owner, group)
682
683        def _walk(base, name, pattern, which):
684            if name:
685                fullname = os.path.join(base, name)
686            else:
687                fullname = base
688            for childname in os.listdir(fullname):
689                ful4childname = os.path.join(fullname, childname)
690                relchildname = os.path.join(name, childname)
691                isdir = os.path.isdir(ful4childname)
692                if (pattern is None or fnmatch.fnmatch(childname, pattern)) and which[isdir]:
693                    url = urllib.pathname2url(relchildname)
694                    if isdir:
695                        url += "/"
696                    yield url
697                if isdir:
698                    for subchild in _walk(base, relchildname, pattern, which):
699                        yield subchild
700
701        def walk(filename, pattern=None):
702            return _walk(filename, "", pattern, (True, True))
703
704        def walkfiles(filename, pattern=None):
705            return _walk(filename, "", pattern, (True, False))
706
707        def walkdirs(filename, pattern=None):
708            return _walk(filename, "", pattern, (False, True))
709
710        while True:
711            (filename, cmdname, args, kwargs) = channel.receive()
712            if isinstance(filename, basestring):
713                filename = os.path.expanduser(urllib.url2pathname(filename))
714            data = None
715            try:
716                if cmdname == "open":
717                    try:
718                        stream = open(filename, *args, **kwargs)
719                    except IOError, exc:
720                        if "w" not in args[0] or exc[0] != 2: # didn't work for some other reason than a non existing directory
721                            raise
722                        (splitpath, splitname) = os.path.split(filename)
723                        if splitpath:
724                            os.makedirs(splitpath)
725                            stream = open(filename, *args, **kwargs)
726                        else:
727                            raise # we don't have a directory to make so pass the error on
728                    data = id(stream)
729                    files[data] = stream
730                elif cmdname == "stat":
731                    if isinstance(filename, basestring):
732                        data = tuple(os.stat(filename))
733                    else:
734                        data = tuple(os.fstat(files[filename].fileno()))
735                elif cmdname == "lstat":
736                    data = os.lstat(filename)
737                elif cmdname == "close":
738                    try:
739                        stream = files[filename]
740                    except KeyError:
741                        pass
742                    else:
743                        stream.close()
744                        del files[filename]
745                elif cmdname == "chmod":
746                    data = os.chmod(filename, *args, **kwargs)
747                elif cmdname == "chown":
748                    (owner, group) = ownergroup(filename, *args, **kwargs)
749                    if owner is not None:
750                        data = os.chown(filename, owner, group)
751                elif cmdname == "lchown":
752                    (owner, group) = ownergroup(filename, *args, **kwargs)
753                    if owner is not None:
754                        data = os.lchown(filename, owner, group)
755                elif cmdname == "uid":
756                    stat = os.stat(filename)
757                    data = stat.st_uid
758                elif cmdname == "gid":
759                    stat = os.stat(filename)
760                    data = stat.st_gid
761                elif cmdname == "owner":
762                    import pwd
763                    stat = os.stat(filename)
764                    data = pwd.getpwuid(stat.st_uid)[0]
765                elif cmdname == "group":
766                    import grp
767                    stat = os.stat(filename)
768                    data = grp.getgrgid(stat.st_gid)[0]
769                elif cmdname == "exists":
770                    data = os.path.exists(filename)
771                elif cmdname == "isfile":
772                    data = os.path.isfile(filename)
773                elif cmdname == "isdir":
774                    data = os.path.isdir(filename)
775                elif cmdname == "islink":
776                    data = os.path.islink(filename)
777                elif cmdname == "ismount":
778                    data = os.path.ismount(filename)
779                elif cmdname == "access":
780                    data = os.access(filename, *args, **kwargs)
781                elif cmdname == "remove":
782                    data = os.remove(filename)
783                elif cmdname == "rmdir":
784                    data = os.rmdir(filename)
785                elif cmdname == "rename":
786                    data = os.rename(filename, os.path.expanduser(args[0]))
787                elif cmdname == "link":
788                    data = os.link(filename, os.path.expanduser(args[0]))
789                elif cmdname == "symlink":
790                    data = os.symlink(filename, os.path.expanduser(args[0]))
791                elif cmdname == "chdir":
792                    data = os.chdir(filename)
793                elif cmdname == "mkdir":
794                    data = os.mkdir(filename)
795                elif cmdname == "makedirs":
796                    data = os.makedirs(filename)
797                elif cmdname == "makefifo":
798                    data = os.makefifo(filename)
799                elif cmdname == "listdir":
800                    data = []
801                    for f in os.listdir(filename):
802                        if args[0] is None or fnmatch.fnmatch(f, args[0]):
803                            data.append((os.path.isdir(os.path.join(filename, f)), f))
804                elif cmdname == "files":
805                    data = []
806                    for f in os.listdir(filename):
807                        if args[0] is None or fnmatch.fnmatch(f, args[0]):
808                            if os.path.isfile(os.path.join(filename, f)):
809                                data.append(f)
810                elif cmdname == "dirs":
811                    data = []
812                    for f in os.listdir(filename):
813                        if args[0] is None or fnmatch.fnmatch(f, args[0]):
814                            if os.path.isdir(os.path.join(filename, f)):
815                                data.append(f)
816                elif cmdname == "walk":
817                    iterator = walk(filename, *args, **kwargs)
818                    data = id(iterator)
819                    iterators[data] = iterator
820                elif cmdname == "walkfiles":
821                    iterator = walkfiles(filename, *args, **kwargs)
822                    data = id(iterator)
823                    iterators[data] = iterator
824                elif cmdname == "walkdirs":
825                    iterator = walkdirs(filename, *args, **kwargs)
826                    data = id(iterator)
827                    iterators[data] = iterator
828                elif cmdname == "iteratornext":
829                    try:
830                        data = iterators[filename].next()
831                    except StopIteration:
832                        del iterators[filename]
833                        raise
834                else:
835                    data = getattr(files[filename], cmdname)
836                    data = data(*args, **kwargs)
837            except Exception, exc:
838                if exc.__class__.__module__ != "exceptions":
839                    raise
840                channel.send((True, cPickle.dumps(exc)))
841            else:
842                channel.send((False, data))
843    """
844    def __init__(self, context, server, remotepython=None, nice=None):
845        # We don't have to store the context (this avoids cycles)
846        self.server = server
847        self.remotepython = remotepython
848        self.nice = nice
849        self._channel = None
850
851    def close(self):
852        if self._channel is not None and not self._channel.isclosed():
853            self._channel.close()
854            self._channel.gateway.exit()
855            self._channel.gateway.join()
856
857    def _url2filename(self, url):
858        if url.scheme != "ssh":
859            raise ValueError("URL {0!r} is not an ssh URL".format(url))
860        filename = str(url.path)
861        if filename.startswith("/~"):
862            filename = filename[1:]
863        return filename
864
865    def _send(self, filename, cmd, *args, **kwargs):
866        if self._channel is None:
867            server = "ssh={0}".format(self.server)
868            if self.remotepython is not None:
869                server += "//python={0}".format(self.remotepython)
870            if self.nice is not None:
871                server += "//nice={0}".format(self.nice)
872            gateway = execnet.makegateway(server) # This requires ``execnet`` (http://codespeak.net/execnet/)
873            self._channel = gateway.remote_exec(self.remote_code)
874        self._channel.send((filename, cmd, args, kwargs))
875        (isexc, data) = self._channel.receive()
876        if isexc:
877            raise cPickle.loads(data)
878        else:
879            return data
880
881    def stat(self, url):
882        filename = self._url2filename(url)
883        data = self._send(filename, "stat")
884        return os.stat_result(data) # channel returned a tuple => wrap it
885
886    def lstat(self):
887        filename = self._url2filename(url)
888        data = self._send(filename, "lstat")
889        return os.stat_result(data) # channel returned a tuple => wrap it
890
891    def chmod(self, url, mode):
892        return self._send(self._url2filename(url), "chmod", mode)
893
894    def chown(self, url, owner=None, group=None):
895        return self._send(self._url2filename(url), "chown", owner, group)
896
897    def lchown(self, url, owner=None, group=None):
898        return self._send(self._url2filename(url), "lchown", owner, group)
899
900    def chdir(self, url):
901        return self._send(self._url2filename(url), "chdir")
902
903    def mkdir(self, url, mode=0777):
904        return self._send(self._url2filename(url), "mkdir", mode)
905
906    def makedirs(self, url, mode=0777):
907        return self._send(self._url2filename(url), "makedirs", mode)
908
909    def uid(self, url):
910        return self._send(self._url2filename(url), "uid")
911
912    def gid(self, url):
913        return self._send(self._url2filename(url), "gid")
914
915    def owner(self, url):
916        return self._send(self._url2filename(url), "owner")
917
918    def group(self, url):
919        return self._send(self._url2filename(url), "group")
920
921    def exists(self, url):
922        return self._send(self._url2filename(url), "exists")
923
924    def isfile(self, url):
925        return self._send(self._url2filename(url), "isfile")
926
927    def isdir(self, url):
928        return self._send(self._url2filename(url), "isdir")
929
930    def islink(self, url):
931        return self._send(self._url2filename(url), "islink")
932
933    def ismount(self, url):
934        return self._send(self._url2filename(url), "ismount")
935
936    def access(self, url, mode):
937        return self._send(self._url2filename(url), "access", mode)
938
939    def remove(self, url):
940        return self._send(self._url2filename(url), "remove")
941
942    def rmdir(self, url):
943        return self._send(self._url2filename(url), "rmdir")
944
945    def _cmdwithtarget(self, cmdname, url, target):
946        filename = self._url2filename(url)
947        if not isinstance(target, URL):
948            target = URL(target)
949        targetname = self._url2filename(target)
950        if target.server != url.server:
951            raise OSError(errno.EXDEV, os.strerror(errno.EXDEV))
952        return self._send(filename, cmdname, targetname)
953
954    def rename(self, url, target):
955        return self._cmdwithtarget("rename", url, target)
956
957    def link(self, url, target):
958        return self._cmdwithtarget("link", url, target)
959
960    def symlink(self, url, target):
961        return self._cmdwithtarget("symlink", url, target)
962
963    def listdir(self, url, pattern=None):
964        filename = self._url2filename(url)
965        result = []
966        for (isdir, name) in self._send(filename, "listdir", pattern):
967            name = urllib.pathname2url(name)
968            if isdir:
969                name += "/"
970            result.append(URL(name))
971        return result
972
973    def files(self, url, pattern=None):
974        filename = self._url2filename(url)
975        return [URL(urllib.pathname2url(name)) for name in self._send(filename, "files", pattern)]
976
977    def dirs(self, url, pattern=None):
978        filename = self._url2filename(url)
979        return [URL(urllib.pathname2url(name)+"/") for name in self._send(filename, "dirs", pattern)]
980
981    def walk(self, url, pattern=None):
982        filename = self._url2filename(url)
983        iterator = self._send(filename, "walk", pattern)
984        while True:
985            yield URL(self._send(iterator, "iteratornext"))
986
987    def walkfiles(self, url, pattern=None):
988        filename = self._url2filename(url)
989        iterator = self._send(filename, "walkfiles", pattern)
990        while True:
991            yield URL(self._send(iterator, "iteratornext"))
992
993    def walkdirs(self, url, pattern=None):
994        filename = self._url2filename(url)
995        iterator = self._send(filename, "walkdirs", pattern)
996        while True:
997            yield URL(self._send(iterator, "iteratornext"))
998
999    def open(self, url, mode="rb"):
1000        return RemoteFileResource(self, url, mode)
1001
1002    def __repr__(self):
1003        return "<{0.__class__.__module__}.{0.__class__.__name__} to {0.server!r} at {1:#x}>".format(self, id(self))
1004
1005
1006class URLConnection(Connection):
1007    def mimetype(self, url):
1008        return url.open().mimetype()
1009
1010    def size(self, url):
1011        return url.open().size()
1012
1013    def imagesize(self, url):
1014        return url.open().imagesize()
1015
1016    def mdate(self, url):
1017        return url.open().mdate()
1018
1019    def resheaders(self, url):
1020        return url.open().resheaders()
1021
1022    def open(self, url, mode="rb", headers=None, data=None):
1023        if mode != "rb":
1024            raise NotImplementedError("mode {0!r} not supported".format(mode))
1025        return URLResource(url, headers=headers, data=data)
1026
1027
1028def here(scheme="file"):
1029    """
1030    Return the current directory as an :class:`URL` object.
1031    """
1032    return Dir(os.getcwd(), scheme)
1033
1034
1035def home(user="", scheme="file"):
1036    """
1037    Return the home directory of the current user (or the user named :var:`user`,
1038    if :var:`user` is specified) as an :class:`URL` object::
1039
1040        >>> url.home()
1041        URL('file:/home/walter/')
1042        >>> url.home("andreas")
1043        URL('file:/home/andreas/')
1044    """
1045    return Dir("~{0}".format(user), scheme)
1046
1047
1048def root():
1049    """
1050    Return a blank ``root`` :class:`URL`, i.e. ``URL("root:")``.
1051    """
1052    return URL("root:")
1053
1054
1055def File(name, scheme="file"):
1056    """
1057    Turn a filename into an :class:`URL` object::
1058
1059        >>> url.File("a#b")
1060        URL('file:a%23b')
1061    """
1062    name = urllib.pathname2url(os.path.expanduser(name))
1063    if name.startswith("///"):
1064        name = name[2:]
1065    url = URL(name)
1066    url.scheme = scheme
1067    return url
1068
1069
1070def Dir(name, scheme="file"):
1071    """
1072    Turns a directory name into an :class:`URL` object, just like :func:`File`,
1073    but ensures that the path is terminated with a ``/``::
1074
1075        >>> url.Dir("a#b")
1076        URL('file:a%23b/')
1077    """
1078    name = urllib.pathname2url(os.path.expanduser(name))
1079    if not name.endswith("/"):
1080        name += "/"
1081    if name.startswith("///"):
1082        name = name[2:]
1083    url = URL(name)
1084    url.scheme = scheme
1085    return url
1086
1087
1088def Ssh(user, host, path="~/"):
1089    """
1090    Return a ssh :class:`URL` for the user :var:`user` on the host :var:`host`
1091    with the path :var:`path`.:var:`path` (defaulting to the users home
1092    directory) must be a path in URL notation (i.e. use ``/`` as directory
1093    separator)::
1094
1095        >>> url.Ssh("root", "www.example.com", "~joe/public_html/index.html")
1096        URL('ssh://root@www.example.com/~joe/public_html/index.html')
1097
1098    If the path starts with ``~/`` it is relative to this users home directory,
1099    if it starts with ``~user`` it's relative to the home directory of the user
1100    ``user``. In all othercases the path is considered to be absolute.
1101    """
1102    url = URL()
1103    url.scheme = "ssh"
1104    url.userinfo = user
1105    url.host = host
1106    if path.startswith("~"):
1107        path = "/" + path
1108    url.path = path
1109    return url
1110
1111
1112def first(urls):
1113    """
1114    Return the first URL from :var:`urls` that exists as a real file or
1115    directory. :const:`None` entries in :var:`urls` will be skipped.
1116    """
1117    for url in urls:
1118        if url is not None:
1119            if url.exists():
1120                return url
1121
1122
1123def firstdir(urls):
1124    """
1125    Return the first URL from :var:`urls` that exists as a real directory.
1126    :const:`None` entries in :var:`urls` will be skipped.
1127    """
1128    for url in urls:
1129        if url is not None:
1130            if url.isdir():
1131                return url
1132
1133
1134def firstfile(urls):
1135    """
1136    Return the first URL from :var:`urls` that exists as a real file.
1137    :const:`None` entries in :var:`urls` will be skipped.
1138    """
1139    for url in urls:
1140        if url is not None:
1141            if url.isfile():
1142                return url
1143
1144
1145class Resource(object):
1146    """
1147    A :class:`Resource` is a base class that provides a file-like interface
1148    to local and remote files, URLs and other resources.
1149
1150    Attributes
1151    ----------
1152    Each resource object has the following attributes:
1153
1154        :attr:`url`
1155            The URL for which this resource has been opened (i.e.
1156            ``foo.open().url is foo`` if ``foo`` is a :class:`URL` object);
1157
1158        :attr:`name`
1159            A string version of :attr:`url`;
1160
1161        :attr:`closed`
1162            A :class:`bool` specifying whether the resource has been closed
1163            (i.e. whether the :meth:`close` method has been called).
1164
1165    Methods
1166    -------
1167    In addition to file methods (like :meth:`read`, :meth:`readlines`,
1168    :meth:`write` and :meth:`close`) a resource object might provide the
1169    following methods:
1170
1171        :meth:`finalurl`
1172            Return the real URL of the resource (this might be different from the
1173            :attr:`url` attribute in case of a redirect).
1174
1175        :meth:`size`
1176            Return the size of the file/resource.
1177
1178        :meth:`mdate`
1179            Return the last modification date of the file/resource as a
1180            :class:`datetime.datetime` object in UTC.
1181
1182        :meth:`mimetype`
1183            Return the mimetype of the file/resource.
1184
1185        :meth:`imagesize`
1186            Return the size of the image (if the resource is an image file) as a
1187            ``(width, height)`` tuple. This requires the PIL__.
1188
1189            __ http://www.pythonware.com/products/pil/
1190    """
1191
1192    def finalurl(self):
1193        return self.url
1194
1195    def imagesize(self):
1196        pos = self.tell()
1197        self.seek(0)
1198        img = Image.open(self) # Requires PIL
1199        imagesize = img.size
1200        self.seek(pos)
1201        return imagesize
1202
1203    def __repr__(self):
1204        return "<{0}{1.__class__.__module__}.{1.__class__.__name__} {1.name}, mode {1.mode} at {2:#x}>".format("closed" if self.closed else "open", self, id(self))
1205
1206
1207class FileResource(Resource, file):
1208    """
1209    A subclass of :class:`Resource` that handles local files.
1210    """
1211    def __init__(self, url, mode="rb"):
1212        url = URL(url)
1213        name = os.path.expanduser(url.local())
1214        try:
1215            file.__init__(self, name, mode)
1216        except IOError, exc:
1217            if "w" not in mode or exc[0] != 2: # didn't work for some other reason than a non existing directory
1218                raise
1219            (splitpath, splitname) = os.path.split(name)
1220            if splitpath:
1221                os.makedirs(splitpath)
1222                file.__init__(self, name, mode)
1223            else:
1224                raise # we don't have a directory to make so pass the error on
1225        self.url = url
1226
1227    def size(self):
1228        # Forward to the connection
1229        return LocalSchemeDefinition._connection.size(self.url)
1230
1231    def mdate(self):
1232        # Forward to the connection
1233        return LocalSchemeDefinition._connection.mdate(self.url)
1234
1235    def mimetype(self):
1236        # Forward to the connection
1237        return LocalSchemeDefinition._connection.mimetype(self.url)
1238
1239
1240class RemoteFileResource(Resource):
1241    """
1242    A subclass of :class:`Resource` that handles remote files (those using
1243    the ``ssh`` scheme).
1244    """
1245    def __init__(self, connection, url, mode="rb"):
1246        self.connection = connection
1247        self.url = URL(url)
1248        self.mode = mode
1249        self.closed = False
1250        filename = self.connection._url2filename(url)
1251        self.name = str(self.url)
1252        self.remoteid = self._send(filename, "open", mode)
1253
1254    def _send(self, filename, cmd, *args, **kwargs):
1255        if self.closed:
1256            raise ValueError("I/O operation on closed file")
1257        return self.connection._send(filename, cmd, *args, **kwargs)
1258
1259    def close(self):
1260        if not self.closed:
1261            self._send(self.remoteid, "close")
1262            self.connection = None # close the channel too as there are no longer any meaningful operations
1263            self.closed = True
1264
1265    def read(self, size=-1):
1266        return self._send(self.remoteid, "read", size)
1267
1268    def readline(self, size=-1):
1269        return self._send(self.remoteid, "readline", size)
1270
1271    def readlines(self, size=-1):
1272        return self._send(self.remoteid, "readlines", size)
1273
1274    def __iter__(self):
1275        return self
1276
1277    def next(self):
1278        return self._send(self.remoteid, "next")
1279
1280    def seek(self, offset, whence=0):
1281        return self._send(self.remoteid, "seek", offset, whence)
1282
1283    def tell(self):
1284        return self._send(self.remoteid, "tell")
1285
1286    def truncate(self, size=None):
1287        if size is None:
1288            return self._send(self.remoteid, "truncate")
1289        else:
1290            return self._send(self.remoteid, "truncate", size)
1291
1292    def write(self, string):
1293        return self._send(self.remoteid, "write", string)
1294
1295    def writelines(self, strings):
1296        return self._send(self.remoteid, "writelines", strings)
1297
1298    def flush(self):
1299        return self._send(self.remoteid, "flush")
1300
1301    def size(self):
1302        # Forward to the connection
1303        return self.connection.size(self.url)
1304
1305    def mdate(self):
1306        # Forward to the connection
1307        return self.connection.mdate(self.url)
1308
1309    def mimetype(self):
1310        # Forward to the connection
1311        return self.connection.mimetype(self.url)
1312
1313
1314class URLResource(Resource):
1315    """
1316    A subclass of :class:`Resource` that handles HTTP, FTP and other URLs
1317    (i.e. those that are not handled by :class:`FileResource` or
1318    :class:`RemoteFileResource`.
1319    """
1320    def __init__(self, url, mode="rb", headers=None, data=None):
1321        if "w" in mode:
1322            raise ValueError("writing mode {0!r} not supported".format(mode))
1323        self.url = URL(url)
1324        self.name = str(self.url)
1325        self.mode = mode
1326        self.reqheaders = headers
1327        self.reqdata = data
1328        self._finalurl = None
1329        self.closed = False
1330        self._stream = None
1331        if data is not None:
1332            data = urllib.urlencode(data)
1333        if headers is None:
1334            headers = {}
1335        req = urllib2.Request(url=self.name, data=data, headers=headers)
1336        self._stream = urllib2.urlopen(req)
1337        self._finalurl = URL(self._stream.url) # Remember the final URL in case of a redirect
1338        self._resheaders = self._stream.info()
1339        self._mimetype = None
1340        self._encoding = None
1341        contenttype = self._resheaders.getheader("Content-Type")
1342        if contenttype is not None:
1343            (mimetype, options) = cgi.parse_header(contenttype)
1344            self._mimetype = mimetype
1345            self._encoding = options.get("charset")
1346
1347        cl = self._resheaders.get("Content-Length")
1348        if cl:
1349            cl = int(cl)
1350        self._size = cl
1351        lm = self._resheaders.get("Last-Modified")
1352        if lm is not None:
1353            lm = mime2dt(lm)
1354        self._mdate = lm
1355        self._buffer = cStringIO.StringIO()
1356
1357    def __getattr__(self, name):
1358        function = getattr(self._stream, name)
1359        def call(*args, **kwargs):
1360            return function(*args, **kwargs)
1361        return call
1362
1363    def close(self):
1364        if not self.closed:
1365            self._stream.close()
1366            self._stream = None
1367            self.closed = True
1368
1369    def __iter__(self):
1370        return iter(self._stream)
1371
1372    def finalurl(self):
1373        return self._finalurl
1374
1375    def mimetype(self):
1376        return self._mimetype
1377
1378    def resheaders(self):
1379        return self._resheaders
1380
1381    def encoding(self):
1382        return self._encoding
1383
1384    def mdate(self):
1385        return self._mdate
1386
1387    def size(self):
1388        return self._size
1389
1390    def read(self, size=-1):
1391        data = self._stream.read(size)
1392        self._buffer.write(data)
1393        return data
1394
1395    def readline(self, size=-1):
1396        data = self._stream.readline(size)
1397        self._buffer.write(data)
1398        return data
1399
1400    def resdata(self):
1401        data = self._stream.read()
1402        self._buffer.write(data)
1403        return self._buffer.getvalue()
1404
1405    def imagesize(self):
1406        img = Image.open(cStringIO.StringIO(self.resdata())) # Requires PIL
1407        return img.size
1408
1409    def __iter__(self):
1410        while True:
1411            data = self._stream.readline()
1412            if not data:
1413                break
1414            self._buffer.write(data)
1415            yield data
1416
1417
1418class SchemeDefinition(object):
1419    """
1420    A :class:`SchemeDefinition` instance defines the properties of a particular
1421    URL scheme.
1422    """
1423    _connection = URLConnection()
1424
1425    def __init__(self, scheme, usehierarchy, useserver, usefrag, islocal=False, isremote=False, defaultport=None):
1426        """
1427        Create a new :class:`SchemeDefinition` instance. Arguments are:
1428
1429        *   :var:`scheme`: The name of the scheme;
1430
1431        *   :var:`usehierarchy`: Specifies whether this scheme uses hierarchical
1432            URLs or opaque URLs (i.e. whether ``hier_part`` or ``opaque_part``
1433            from the BNF in :rfc:`2396` is used);
1434
1435        *   :var:`useserver`: Specifies whether this scheme uses an Internet-based
1436            server :prop:`authority` component or a registry of naming authorities
1437            (only for hierarchical URLs);
1438
1439        *   :var:`usefrag`: Specifies whether this scheme uses fragments
1440            (according to the BNF in :rfc:`2396` every scheme does, but it doesn't
1441            make sense for e.g. ``"javascript"``, ``"mailto"`` or ``"tel"``);
1442
1443        *   :var:`islocal`: Specifies whether URLs with this scheme refer to
1444            local files;
1445
1446        *   :var:`isremote`: Specifies whether URLs with this scheme refer to
1447            remote files (there may be schemes which are neither local nor remote,
1448            e.g. ``"mailto"``);
1449
1450        *   :var:`defaultport`: The default port for this scheme (only for schemes
1451            using server based authority).
1452        """
1453        self.scheme = scheme
1454        self.usehierarchy = usehierarchy
1455        self.useserver = useserver
1456        self.usefrag = usefrag
1457        self.islocal = islocal
1458        self.isremote = isremote
1459        self.defaultport = defaultport
1460
1461    def connect(self, url, context=None, **kwargs):
1462        """
1463        Create a :class:`Connection` for the :class:`URL` :var:`url` (which must
1464        have :var:`self` as the scheme).
1465        """
1466        return self._connect(url, context, **kwargs)[0]
1467
1468    def _connect(self, url, context=None, **kwargs):
1469        # Returns a tuple ``(connect, kwargs)`` (some of the keyword arguments
1470        # might have been consumed by the connect call, the rest can be passed
1471        # on the whatever call will be made on the connection itself)
1472        # We can always use the same connection here, because the connection for
1473        # local files and real URLs doesn't use any resources.
1474        # This will be overwritten by :class:`SshSchemeDefinition`
1475        return (self._connection, kwargs)
1476
1477    def open(self, *args, **kwargs):
1478        return URLConnection(*args, **kwargs)
1479
1480    def closeall(self, context):
1481        """
1482        Close all connections active for this scheme in the context :var:`context`.
1483        """
1484
1485    def __repr__(self):
1486        return "<{0.__class__.__name__} instance scheme={0.scheme!r} usehierarchy={0.usehierarchy!r} useserver={0.useserver!r} usefrag={0.usefrag!r} at {1:#x}>".format(self, id(self))
1487
1488
1489class LocalSchemeDefinition(SchemeDefinition):
1490    # Use a different connection than the base class (but still one single connection for all URLs)
1491    _connection = LocalConnection()
1492
1493    def open(self, *args, **kwargs):
1494        return FileResource(*args, **kwargs)
1495
1496
1497class SshSchemeDefinition(SchemeDefinition):
1498    def _connect(self, url, context=None, **kwargs):
1499        if "remotepython" in kwargs or "nice" in kwargs:
1500            kwargs = kwargs.copy()
1501            remotepython = kwargs.pop("remotepython", None)
1502            nice = kwargs.pop("nice", None)
1503        else:
1504            remotepython = None
1505            nice = None
1506
1507        context = getcontext(context)
1508        if context is threadlocalcontext.__class__.context:
1509            raise ValueError("ssh URLs need a custom context")
1510        # Use one :class:`SshConnection` for each user/host/remotepython combination
1511        server = url.server
1512        try:
1513            connections = context.schemes["ssh"]
1514        except KeyError:
1515            connections = context.schemes["ssh"] = {}
1516        try:
1517            connection = connections[(server, remotepython, nice)]
1518        except KeyError:
1519            connection = connections[(server, remotepython, nice)] = SshConnection(context, server, remotepython, nice)
1520        return (connection, kwargs)
1521
1522    def open(self, url, mode="rb", context=None, remotepython=None, nice=None):
1523        (connection, kwargs) = self._connect(url, context, remotepython=remotepython, nice=nice)
1524        return RemoteFileResource(connection, url, mode, **kwargs)
1525
1526    def closeall(self, context):
1527        for connection in context.schemes["ssh"].itervalues():
1528            connection.close()
1529
1530
1531schemereg = {
1532    "http": SchemeDefinition("http", usehierarchy=True, useserver=True, usefrag=True, isremote=True, defaultport=80),
1533    "https": SchemeDefinition("https", usehierarchy=True, useserver=True, usefrag=True, isremote=True, defaultport=443),
1534    "ftp": SchemeDefinition("ftp", usehierarchy=True, useserver=True, usefrag=True, isremote=True, defaultport=21),
1535    "file": LocalSchemeDefinition("file", usehierarchy=True, useserver=False, usefrag=True, islocal=True),
1536    "root": LocalSchemeDefinition("root", usehierarchy=True, useserver=False, usefrag=True, islocal=True),
1537    "javascript": SchemeDefinition("javascript", usehierarchy=False, useserver=False, usefrag=False),
1538    "mailto": SchemeDefinition("mailto", usehierarchy=False, useserver=False, usefrag=False),
1539    "tel": SchemeDefinition("tel", usehierarchy=False, useserver=False, usefrag=False),
1540    "fax": SchemeDefinition("fax", usehierarchy=False, useserver=False, usefrag=False),
1541    "ssh": SshSchemeDefinition("ssh", usehierarchy=True, useserver=True, usefrag=True, islocal=False, isremote=True),
1542}
1543defaultreg = LocalSchemeDefinition("", usehierarchy=True, useserver=True, islocal=True, usefrag=True)
1544
1545
1546class Path(object):
1547    __slots__ = ("_path", "_segments")
1548
1549    def __init__(self, path=None):
1550        self._path = ""
1551        self._segments = []
1552        self.path = path
1553
1554    @classmethod
1555    def _fixsegment(cls, segment):
1556        if isinstance(segment, unicode):
1557            segment = _escape(segment, pathsafe)
1558        return _unescape(segment)
1559
1560    def _prefix(cls, path):
1561        if path.startswith("/"):
1562            return "/"
1563        else:
1564            return ""
1565
1566    def insert(self, index, *others):
1567        segments = self.segments
1568        segments[index:index] = map(self._fixsegment, others)
1569        self.segments = segments
1570
1571    def startswith(self, prefix):
1572        """
1573        Return whether :var:`self` starts with the path :var:`prefix`.
1574        :var:`prefix` will be converted to a :class:`Path` if it isn't one.
1575        """
1576        if not isinstance(prefix, Path):
1577            prefix = Path(prefix)
1578        segments = prefix.segments
1579        if self.isabs != prefix.isabs:
1580            return False
1581        if segments and not segments[-1] and len(self.segments)>len(segments):
1582            return self.segments[:len(segments)-1] == segments[:-1]
1583        else:
1584            return self.segments[:len(segments)] == segments
1585
1586    def endswith(self, suffix):
1587        """
1588        Return whether :var:`self` ends with the path :var:`suffix`. :var:`suffix`
1589        will be converted to a :class:`Path` if it isn't one. If :var:`suffix` is
1590        absolute a normal comparison will be done.
1591        """
1592        if not isinstance(suffix, Path):
1593            suffix = Path(suffix)
1594        if suffix.isabs:
1595            return self == suffix
1596        else:
1597            segments = suffix.segments
1598            return self.segments[-len(segments):] == segments
1599
1600    def clone(self):
1601        return Path(self)
1602
1603    def __repr__(self):
1604        return "Path({0!r})".format(self._path)
1605
1606    def __str__(self):
1607        return self.path
1608
1609    def __eq__(self, other):
1610        if not isinstance(other, Path):
1611            other = Path(other)
1612        return self._path == other._path
1613
1614    def __ne__(self, other):
1615        return not self == other
1616
1617    def __hash__(self):
1618        return hash(self._path)
1619
1620    def __len__(self):
1621        return len(self.segments)
1622
1623    def __getitem__(self, index):
1624        return self.segments[index]
1625
1626    def __setitem__(self, index, value):
1627        segments = self.segments
1628        segments[index] = self._fixsegment(value)
1629        self._path = self._prefix(self._path) + self._segments2path(segments)
1630        self._segments = segments
1631
1632    def __delitem__(self, index):
1633        segments = self.segments
1634        del segments[index]
1635        self._path = self._segments2path(segments)
1636        self._segments = segments
1637
1638    def __contains__(self, item):
1639        return self._fixsegment(item) in self.segments
1640
1641    def __getslice__(self, index1, index2):
1642        """
1643        Return of slice of the path. The resulting path will always be relative,
1644        i.e. the leading ``/`` will be dropped.
1645        """
1646        return Path(self.segments[index1:index2])
1647
1648    def __setslice__(self, index1, index2, seq):
1649        segments = self.segments
1650        segments[index1:index2] = map(self._fixsegment, seq)
1651        self._path = self._prefix(self._path) + self._segments2path(segments)
1652        self._segments = segments
1653
1654    def __delslice__(self, index1, index2):
1655        del self.segments[index1:index2]
1656
1657    class isabs(misc.propclass):
1658        """
1659        Is the path absolute?
1660        """
1661        def __get__(self):
1662            return self._path.startswith("/")
1663
1664        def __set__(self, isabs):
1665            isabs = bool(isabs)
1666            if isabs != self._path.startswith("/"):
1667                if isabs:
1668                    self._path = "/" + self._path
1669                else:
1670                    self._path = self._path[1:]
1671
1672        def __delete__(self):
1673            if self._path.startswith("/"):
1674                self._path = self._path[1:]
1675
1676    @classmethod
1677    def _segments2path(cls, segments):
1678        return "/".join(_escape(segment, pathsafe) for segment in segments)
1679
1680    @classmethod
1681    def _path2segments(cls, path):
1682        if path.startswith("/"):
1683            path = path[1:]
1684        return map(cls._fixsegment, path.split("/"))
1685
1686    def _setpathorsegments(self, path):
1687        if path is None:
1688            self._path = ""
1689            self._segments = []
1690        elif isinstance(path, Path):
1691            self._path = path._path
1692            self._segments = None
1693        elif isinstance(path, (list, tuple)):
1694            self._segments = map(self._fixsegment, path)
1695            self._path = self._prefix(self._path) + self._segments2path(self._segments)
1696        else:
1697            if isinstance(path, unicode):
1698                path = _escape(path)
1699            prefix = self._prefix(path)
1700            if prefix:
1701                path = path[1:]
1702            self._segments = self._path2segments(path)
1703            self._path = prefix + self._segments2path(self._segments)
1704
1705    class path(misc.propclass):
1706        """
1707        The complete path as a string.
1708        """
1709        def __get__(self):
1710            return self._path
1711
1712        def __set__(self, path):
1713            self._setpathorsegments(path)
1714
1715        def __delete__(self):
1716            self.clear()
1717
1718    class segments(misc.propclass):
1719        """
1720        The path as a list of (name, param) tuples.
1721        """
1722        def __get__(self):
1723            if self._segments is None:
1724                self._segments = self._path2segments(self._path)
1725            return self._segments
1726
1727        def __set__(self, path):
1728            self._setpathorsegments(path)
1729
1730        def __delete__(self):
1731            self._path = self._prefix(self._path)
1732            self._segments = []
1733
1734    class file(misc.propclass):
1735        """
1736        The filename without the path, i.e. the name part of the last component
1737        of :prop:`path`. The ``baz.html`` part of
1738        ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
1739        """
1740        def __get__(self):
1741            try:
1742                return self[-1]
1743            except IndexError:
1744                return None
1745
1746        def __set__(self, file):
1747            """
1748            Setting the filename preserves the parameter in the last segment.
1749            """
1750            if file is None:
1751                del self.file
1752            segments = self.segments
1753            if segments:
1754                self[-1] = file
1755            else:
1756                self.segments = [file]
1757
1758        def __delete__(self):
1759            """
1760            Deleting the filename preserves the parameter in the last segment.
1761            """
1762            segments = self.segments
1763            if segments:
1764                self[-1] = ""
1765
1766    class ext(misc.propclass):
1767        """
1768        The filename extension of the last segment of the path. The ``html`` part
1769        of ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
1770        """
1771        def __get__(self):
1772            ext = None
1773            segments = self.segments
1774            if segments:
1775                segment = segments[-1]
1776                pos = segment.rfind(".")
1777                if pos != -1:
1778                    ext = segment[pos+1:]
1779            return ext
1780
1781        def __set__(self, ext):
1782            if ext is None:
1783                del self.ext
1784            segments = self.segments
1785            if segments:
1786                segment = segments[-1]
1787                pos = segment.rfind(".")
1788                if pos != -1:
1789                    segment = segment[:pos+1] + ext
1790                else:
1791                    segment = segment + "." + ext
1792                self[-1] = segment
1793
1794        def __delete__(self):
1795            segments = self.segments
1796            if segments:
1797                segment = segments[-1]
1798                pos = segment.rfind(".")
1799                if pos != -1:
1800                    segment = segment[:pos]
1801                    self[-1] = segment
1802
1803    def withext(self, ext):
1804        """
1805        Return a new :class:`Path` where the filename extension has been replaced
1806        with :var:`ext`.
1807        """
1808        path = self.clone()
1809        path.ext = ext
1810        return path
1811
1812    def withoutext(self):
1813        """
1814        Return a new :class:`Path` where the filename extension has been removed.
1815        """
1816        if "/" not in self._path and self._path.rfind(".")==0:
1817            return Path("./")
1818        else:
1819            path = self.clone()
1820            del path.ext
1821            return path
1822
1823    def withfile(self, file):
1824        """
1825        Return a new :class:`Path` where the filename (i.e. the name of the last
1826        component of :prop:`segments`) has been replaced with :var:`file`.
1827        """
1828        path = self.clone()
1829        path.file = file
1830        return path
1831
1832    def withoutfile(self):
1833        """
1834        Return a new :class:`Path` where the filename (i.e. the name of the last
1835        component of :prop:`segments`) has been removed.
1836        """
1837        if "/" not in self._path:
1838            return Path("./")
1839        else:
1840            path = Path(self)
1841            del path.file
1842            return path
1843
1844    def clear(self):
1845        self._path = ""
1846        self._segments = []
1847
1848    def __div__(self, other):
1849        """
1850        Join two paths.
1851        """
1852        if isinstance(other, basestring):
1853            other = Path(other)
1854        if isinstance(other, Path):
1855            newpath = Path()
1856            # RFC2396, Section 5.2 (5)
1857            if other.isabs:
1858                newpath._path = other._path
1859                newpath._segments = None
1860            else:
1861                # the following should be equivalent to RFC2396, Section 5.2 (6) (c)-(f)
1862                newpath._path = self._prefix(self._path) + self._segments2path(
1863                    _normalizepath(
1864                        self.segments[:-1] + # RFC2396, Section 5.2 (6) (a)
1865                        other.segments # RFC2396, Section 5.2 (6) (b)
1866                    )
1867                )
1868                newpath._segments = None
1869            return newpath
1870        elif isinstance(other, (list, tuple)): # this makes path/list possible
1871            return other.__class__(self/path for path in other)
1872        else: # this makes path/generator possible
1873            return (self/path for path in other)
1874
1875    def __rdiv__(self, other):
1876        """
1877        Right hand version of :meth:`__div__`. This supports list and generators
1878        as the left hand side too.
1879        """
1880        if isinstance(other, basestring):
1881            other = Path(other)
1882        if isinstance(other, Path):
1883            return other/self
1884        elif isinstance(other, (list, tuple)):
1885            return other.__class__(path/self for path in other)
1886        else:
1887            return (path/self for path in other)
1888
1889    def relative(self, basepath):
1890        """
1891        Return an relative :class:`Path` :var:`rel` such that
1892        ``basepath/rel == self```, i.e. this is the inverse operation of
1893        :meth:`__div__`.
1894
1895        If :var:`self` is relative, an identical copy of :var:`self` will be
1896        returned.
1897        """
1898        # if :var:`self` is relative don't do anything
1899        if not self.isabs:
1900            pass # FIXME return self.clone()
1901        basepath = Path(basepath) # clone/coerce
1902        self_segments = _normalizepath(self.segments)
1903        base_segments = _normalizepath(basepath.segments)
1904        while len(self_segments)>1 and len(base_segments)>1 and self_segments[0]==base_segments[0]:
1905            del self_segments[0]
1906            del base_segments[0]
1907        # build a path from one file to the other
1908        self_segments[:0] = [u".."]*(len(base_segments)-1)
1909        if not len(self_segments) or self_segments==[u""]:
1910            self_segments = [u".", u""]
1911        return Path(self._segments2path(self_segments))
1912
1913    def reverse(self):
1914        segments = self.segments
1915        segments.reverse()
1916        if segments and not segments[0]:
1917            del segments[0]
1918            segments.append(u"")
1919        self.segments = segments
1920
1921    def normalize(self):
1922        self.segments = _normalizepath(self.segments)
1923
1924    def normalized(self):
1925        new = self.clone()
1926        new.normalize()
1927        return new
1928
1929    def local(self):
1930        """
1931        Return :var:`self` converted to a filename using the file naming
1932        conventions of the OS. Parameters will be dropped in the resulting string.
1933        """
1934        path = Path(self._prefix(self._path) + "/".join(segment for segment in self))
1935        path = path._path
1936        localpath = urllib.url2pathname(path)
1937        if path.endswith("/") and not (localpath.endswith(os.sep) or (os.altsep is not None and localpath.endswith(os.altsep))):
1938            localpath += os.sep
1939        return localpath
1940
1941    def abs(self):
1942        """
1943        Return an absolute version of :var:`self`.
1944        """
1945        path = os.path.abspath(self.local())
1946        path = path.rstrip(os.sep)
1947        if path.startswith("///"):
1948            path = path[2:]
1949        path = urllib.pathname2url(path.encode("utf-8"))
1950        if len(self) and not self.segments[-1]:
1951            path += "/"
1952        return Path(path)
1953
1954    def real(self):
1955        """
1956        Return the canonical version of :var:`self`, eliminating all symbolic
1957        links.
1958        """
1959        path = os.path.realpath(self.local())
1960        path = path.rstrip(os.sep)
1961        path = urllib.pathname2url(path.encode("utf-8"))
1962        if path.startswith("///"):
1963            path = path[2:]
1964        if len(self) and not self.segments[-1]:
1965            path += "/"
1966        return Path(path)
1967
1968
1969class Query(dict):
1970    __slots__= ()
1971    def __init__(self, arg=None, **kwargs):
1972        if arg is not None:
1973            if isinstance(arg, dict):
1974                for (key, value) in arg.iteritems():
1975                    self.add(key, value)
1976            else:
1977                for (key, value) in arg:
1978                    self.add(key, value)
1979        for (key, value) in kwargs.iteritems():
1980            self.add(key, value)
1981
1982    def __setitem__(self, key, value):
1983        dict.__setitem__(self, unicode(key), [unicode(value)])
1984
1985    def add(self, key, *values):
1986        key = unicode(key)
1987        values = map(unicode, values)
1988        self.setdefault(key, []).extend(values)
1989
1990    def __xrepr__(self, mode="default"):
1991        if mode == "cell":
1992            yield (astyle.style_url, str(self))
1993        else:
1994            yield (astyle.style_url, repr(self))
1995
1996
1997class URL(object):
1998    """
1999    An :rfc:`2396` compliant URL.
2000    """
2001    def __init__(self, url=None):
2002        """
2003        Create a new :class:`URL` instance. :var:`url` may be a :class:`str` or
2004        :class:`unicode` instance, or an :class:`URL` (in which case you'll get a
2005        copy of :var:`url`), or :const:`None` (which will create an :class:`URL`
2006        referring to the "current document").
2007        """
2008        self.url = url
2009
2010    def _clear(self):
2011        # internal helper method that makes :var:`self` empty.
2012        self.reg = defaultreg
2013        self._scheme = None
2014        self._userinfo = None
2015        self._host = None
2016        self._port = None
2017        self._path = Path()
2018        self._reg_name = None
2019        self._query = None
2020        self._query_parts = None
2021        self._opaque_part = None
2022        self._frag = None
2023
2024    def clone(self):
2025        """
2026        Return an identical copy :var:`self`.
2027        """
2028        return URL(self)
2029
2030    @staticmethod
2031    def _checkscheme(scheme):
2032        # Check whether :var:`scheme` contains only legal characters.
2033        if scheme[0] not in schemecharfirst:
2034            return False
2035        for c in scheme[1:]:
2036            if c not in schemechar:
2037                return False
2038        return True
2039
2040    class scheme(misc.propclass):
2041        """
2042        The URL scheme (e.g. ``ftp``, ``ssh``, ``http`` or ``mailto``). The
2043        scheme will be :const:`None` if the URL is a relative one.
2044        """
2045        def __get__(self):
2046            return self._scheme
2047        def __set__(self, scheme):
2048            """
2049            The scheme will be converted to lowercase on setting (if :var:`scheme`
2050            is not :const:`None`, otherwise the scheme will be deleted).
2051            """
2052            if scheme is None:
2053                self._scheme = None
2054            else:
2055                scheme = scheme.lower()
2056                # check if the scheme only has allowed characters
2057                if not self._checkscheme(scheme):
2058                    raise ValueError("Illegal scheme char in scheme {0!r}".format(scheme))
2059                self._scheme = scheme
2060            self.reg = schemereg.get(scheme, defaultreg)
2061        def __delete__(self):
2062            """
2063            Deletes the scheme, i.e. makes the URL relative.
2064            """
2065            self._scheme = None
2066            self.reg = defaultreg
2067
2068    class userinfo(misc.propclass):
2069        """
2070        The user info part of the :class:`URL`; i.e. the ``user`` part of
2071        ``http://user@www.example.com:8080/bar/baz.html;xyzzy?spam=eggs#frag``.
2072        """
2073        def __get__(self):
2074            return self._userinfo
2075        def __set__(self, userinfo):
2076            self._userinfo = userinfo
2077        def __delete__(self):
2078            self._userinfo = None
2079
2080    class host(misc.propclass):
2081        """
2082        The host part of the :class:`URL`; i.e. the ``www.example.com`` part of
2083        ``http://user@www.example.com:8080/bar/baz.html;xyzzy?spam=eggs#frag``.
2084        """
2085        def __get__(self):
2086            return self._host
2087        def __set__(self, host):
2088            if host is not None:
2089                host = host.lower()
2090            self._host = host
2091        def __delete__(self):
2092            self._host = None
2093
2094    class port(misc.propclass):
2095        """
2096        The port number of the :class:`URL` (as an :class:`int`) or :const:`None`
2097        if the :class:`URL` has none. The ``8080`` in
2098        ``http://user@www.example.com:8080/bar/baz.html;xyzzy?spam=eggs#frag``.
2099        """
2100        def __get__(self):
2101            return self._port
2102        def __set__(self, port):
2103            if port is not None:
2104                port = int(port)
2105            self._port = port
2106        def __delete__(self):
2107            self._port = None
2108
2109    class hostport(misc.propclass):
2110        """
2111        The host and (if specified) the port number of the :class:`URL`, i.e. the
2112        ``www.example.com:8080`` in
2113        ``http://user@www.example.com:8080/bar/baz.html;xyzzy?spam=eggs#frag``.
2114        """
2115        def __get__(self):
2116            if self.host is not None:
2117                hostport = _escape(self.host, safe)
2118                if self.port is not None:
2119                    hostport += ":{0}".format(self.port)
2120                return hostport
2121            else:
2122                return None
2123        def __set__(self, hostport):
2124            # find the port number (RFC2396, Section 3.2.2)
2125            if hostport is None:
2126                del self.hostport
2127            else:
2128                del self.port
2129                pos = hostport.rfind(":")
2130                if pos != -1:
2131                    if pos != len(hostport)-1:
2132                        self.port = hostport[pos+1:]
2133                    hostport = hostport[:pos]
2134                self.host = _unescape(hostport)
2135        def __delete__(self):
2136            del self.host
2137            del self.port
2138
2139    class server(misc.propclass):
2140        """
2141        The server part of the :class:`URL`; i.e. the ``user@www.example.com``
2142        part of ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
2143        """
2144        def __get__(self):
2145            if self.hostport is not None:
2146                userinfo = self.userinfo
2147                if userinfo is not None:
2148                    return _escape(userinfo, safe) + "@" + self.hostport
2149                else:
2150                    return self.hostport
2151            else:
2152                return None
2153        def __set__(self, server):
2154            """
2155            Setting the server always works even if the current :prop:`scheme`
2156            does use :prop:`opaque_part` or :prop:`reg_name` but will be ignored
2157            when reassembling the URL for the :prop:`url` property.
2158            """
2159            if server is None:
2160                del self.server
2161            else:
2162                # find the userinfo (RFC2396, Section 3.2.2)
2163                pos = server.find("@")
2164                if pos != -1:
2165                    self.userinfo = _unescape(server[:pos])
2166                    server = server[pos+1:]
2167                else:
2168                    del self.userinfo
2169                self.hostport = server
2170        def __delete__(self):
2171            del self.userinfo
2172            del self.hostport
2173
2174    class reg_name(misc.propclass):
2175        """
2176        The reg_name part of the :class:`URL` for hierarchical schemes that use
2177        a name based :prop:`authority` instead of :prop:`server`.
2178        """
2179        def __get__(self):
2180            return self._reg_name
2181        def __set__(self, reg_name):
2182            if reg_name is None:
2183                del self.reg_name
2184            else:
2185                self._reg_name = reg_name
2186        def __delete__(self):
2187            self._reg_name = None
2188
2189    class authority(misc.propclass):
2190        """
2191        The authority part of the :class:`URL` for hierarchical schemes.
2192        Depending on the scheme, this is either :prop:`server` or
2193        :prop:`reg_name`.
2194        """
2195        def __get__(self):
2196            if self.reg.useserver:
2197                return self.server
2198            else:
2199                return self.reg_name
2200        def __set__(self, authority):
2201            if self.reg.useserver:
2202                self.server = authority
2203            else:
2204                self.reg_name = authority
2205        def __delete__(self):
2206            if self.reg.useserver:
2207                del self.server
2208            else:
2209                del self.reg_name
2210
2211    class isabspath(misc.propclass):
2212        """
2213        Specifies whether the path of a hierarchical :class:`URL` is absolute,
2214        (i.e. it has a leading ``"/"``). Note that the path will always be
2215        absolute if an :prop:`authority` is specified.
2216        """
2217        def __get__(self):
2218            return (self.authority is not None) or self.path.isabs
2219        def __set__(self, isabspath):
2220            self.path.isabs = isabspath
2221
2222    class path(misc.propclass):
2223        """
2224        The path segments of a hierarchical :class:`URL` as a :class:`Path` object.
2225        """
2226        def __get__(self):
2227            return self._path
2228        def __set__(self, path):
2229            self._path = Path(path)
2230        def __delete__(self):
2231            self._path = Path()
2232
2233    class file(misc.propclass):
2234        """
2235        The filename without the path, i.e. the name part of the last component
2236        of :prop:`path`. The ``baz.html`` part of
2237        ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
2238        """
2239        def __get__(self):
2240            return self.path.file
2241        def __set__(self, file):
2242            """
2243            Setting the filename preserves the parameter in the last segment.
2244            """
2245            self.path.file = file
2246        def __delete__(self):
2247            """
2248            Deleting the filename preserves the parameter in the last segment.
2249            """
2250            del self.path.file
2251
2252    class ext(misc.propclass):
2253        """
2254        The filename extension of the last segment of the path. The ``html`` part
2255        of ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
2256        """
2257        def __get__(self):
2258            return self.path.ext
2259        def __set__(self, ext):
2260            """
2261            Setting the extension preserves the parameter in the last segment.
2262            """
2263            self.path.ext = ext
2264        def __delete__(self):
2265            """
2266            Deleting the extension preserves the parameter in the last segment.
2267            """
2268            del self.path.ext
2269
2270    class query_parts(misc.propclass):
2271        """
2272        The query component as a dictionary, i.e. ``{u"spam": u"eggs"}`` from
2273        ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
2274
2275        If the query component couldn't be parsed, ``query_parts`` will be
2276        :const:`False`.
2277        """
2278        def __get__(self):
2279            return self._query_parts
2280        def __set__(self, query_parts):
2281            self._query = _urlencode(query_parts)
2282            self._query_parts = query_parts
2283        def __delete__(self):
2284            self._query = None
2285            self._query_parts = None
2286
2287    class query(misc.propclass):
2288        """
2289        The query component, i.e. the ``spam=eggs`` part of
2290        ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
2291        """
2292        def __get__(self):
2293            if self._query_parts is False:
2294                return self._query
2295            else:
2296                return _urlencode(self._query_parts)
2297        def __set__(self, query):
2298            self._query = query
2299            if query is not None:
2300                parts = {}
2301                for part in query.split(u"&"):
2302                    namevalue = part.split(u"=", 1)
2303                    name = _unescape(namevalue[0].replace("+", " "))
2304                    if len(namevalue) == 2:
2305                        value = _unescape(namevalue[1].replace("+", " "))
2306                        parts.setdefault(name, []).append(value)
2307                    else:
2308                        parts = False
2309                        break
2310                query = parts
2311            self._query_parts = query
2312        def __delete__(self):
2313            self._query = None
2314            self._query_parts = None
2315
2316    class opaque_part(misc.propclass):
2317        """
2318        The opaque part (for schemes like ``mailto`` that are not hierarchical).
2319        """
2320        def __get__(self):
2321            return self._opaque_part
2322        def __set__(self, opaque_part):
2323            self._opaque_part = opaque_part
2324        def __delete__(self):
2325            self._opaque_part = None
2326
2327    class frag(misc.propclass):
2328        """
2329        The fragment identifier, which references a part of the resource, i.e.
2330        the ``frag`` part of
2331        ``http://user@www.example.com/bar/baz.html;xyzzy?spam=eggs#frag``.
2332        """
2333        def __get__(self):
2334            return self._frag
2335        def __set__(self, frag):
2336            self._frag = frag
2337        def __delete__(self):
2338            self._frag = None
2339
2340    class url(misc.propclass):
2341        """
2342        The complete URL
2343        """
2344        def __get__(self):
2345            """
2346            Getting :prop:`url` reassembles the URL from the components.
2347            """
2348            result = ""
2349            if self.scheme is not None:
2350                result += self.scheme + ":"
2351            if self.reg.usehierarchy:
2352                if self.authority is not None:
2353                    result += "//" + self.authority
2354                    if not self.path.isabs:
2355                        result += "/"
2356                result += str(self.path)
2357                if self.query is not None:
2358                    result += "?" + self.query
2359            else:
2360                result += self.opaque_part
2361            if self.reg.usefrag and self.frag is not None:
2362                result += "#" + _escape(self.frag, fragsafe)
2363            return result
2364
2365        def __set__(self, url):
2366            """
2367            Setting :prop:`url` parses :var:`url` into the components. :var:`url`
2368            may also be an :class:`URL` instance, in which case the URL will be
2369            copied.
2370            """
2371            self._clear()
2372            if url is None:
2373                return
2374            elif isinstance(url, URL):
2375                self.scheme = url.scheme
2376                self.userinfo = url.userinfo
2377                self.host = url.host
2378                self.port = url.port
2379                self.path = url.path.clone()
2380                self.reg_name = url.reg_name
2381                self.opaque_part = url.opaque_part
2382                self.query = url.query
2383                self.frag = url.frag
2384            else:
2385                if isinstance(url, unicode):
2386                    url = _escape(url)
2387                # find the scheme (RFC2396, Section 3.1)
2388                pos = url.find(":")
2389                if pos != -1:
2390                    scheme = url[:pos]
2391                    if self._checkscheme(scheme): # if the scheme is illegal assume there is none (e.g. "/foo.php?x=http://www.bar.com", will *not* have the scheme "/foo.php?x=http")
2392                        self.scheme = scheme # the info about what we have to expect in the rest of the URL can be found in self.reg now
2393                        url = url[pos+1:]
2394
2395                # find the fragment (RFC2396, Section 4.1)
2396                if self.reg.usefrag:
2397                    # the fragment itself may not contain a "#", so find the last "#"
2398                    pos = url.rfind("#")
2399                    if pos != -1:
2400                        self.frag = _unescape(url[pos+1:])
2401                        url = url[:pos]
2402
2403                if self.reg.usehierarchy:
2404                    # find the query (RFC2396, Section 3.4)
2405                    pos = url.rfind("?")
2406                    if pos != -1:
2407                        self.query = url[pos+1:]
2408                        url = url[:pos]
2409                    if url.startswith("//"):
2410                        url = url[2:]
2411                        # find the authority part (RFC2396, Section 3.2)
2412                        pos = url.find("/")
2413                        if pos!=-1:
2414                            authority = url[:pos]
2415                            url = url[pos:] # keep the "/"
2416                        else:
2417                            authority = url
2418                            url = "/"
2419                        self.authority = authority
2420                    self.path = Path(url)
2421                else:
2422                    self.opaque_part = url
2423        def __delete__(self):
2424            """
2425            After deleting the URL the resulting object will refer to the
2426            "current document".
2427            """
2428            self._clear()
2429
2430    def withext(self, ext):
2431        """
2432        Return a new :class:`URL` where the filename extension has been replaced
2433        with :var:`ext`.
2434        """
2435        url = URL(self)
2436        url.path = url.path.withext(ext)
2437        return url
2438
2439    def withoutext(self):
2440        """
2441        Return a new :class:`URL` where the filename extension has been removed.
2442        """
2443        url = URL(self)
2444        url.path = url.path.withoutext()
2445        return url
2446
2447    def withfile(self, file):
2448        """
2449        Return a new :class:`URL` where the filename (i.e. the name of last
2450        component of :prop:`path_segments`) has been replaced with
2451        :var:`file`.
2452        """
2453        url = URL(self)
2454        url.path = url.path.withfile(file)
2455        return url
2456
2457    def withoutfile(self):
2458        url = URL(self)
2459        url.path = url.path.withoutfile()
2460        return url
2461
2462    def withfrag(self, frag):
2463        """
2464        Return a new :class:`URL` where the fragment has been replaced with
2465        :var:`frag`.
2466        """
2467        url = URL(self)
2468        url.frag = frag
2469        return url
2470
2471    def withoutfrag(self):
2472        """
2473        Return a new :class:`URL` where the frag has been dropped.
2474        """
2475        url = URL(self)
2476        del url.frag
2477        return url
2478
2479    def __div__(self, other):
2480        """
2481        Join :var:`self` with another (possible relative) :class:`URL`
2482        :var:`other`, to form a new :class:`URL`.
2483
2484        :var:`other` may be a :class:`str`, :class:`unicode` or :class:`URL`
2485        object. It may be :const:`None` (referring to the "current document")
2486        in which case :var:`self` will be returned. It may also be a list or
2487        other iterable. For this case a list (or iterator) will be returned where
2488        :meth:`__div__` will be applied to every item in the list/iterator. E.g.
2489        the following expression returns all the files in the current directory
2490        as absolute URLs (see the method :meth:`files` and the function
2491        :func:`here` for further explanations)::
2492
2493            >>> here = url.here()
2494            >>> for f in here/here.files():
2495            ...     print f
2496        """
2497        if isinstance(other, basestring):
2498            other = URL(other)
2499        if isinstance(other, URL):
2500            newurl = URL()
2501            # RFC2396, Section 5.2 (2)
2502            if other.scheme is None and other.authority is None and str(other.path)=="" and other.query is None:
2503                newurl = URL(self)
2504                newurl.frag = other.frag
2505                return newurl
2506            if not self.reg.usehierarchy: # e.g. "mailto:x@y"/"file:foo"
2507                return other
2508            # In violation of RFC2396 we treat file URLs as relative ones (if the base is a local URL)
2509            if other.scheme=="file" and self.islocal():
2510                other = URL(other)
2511                del other.scheme
2512                del other.authority
2513            # RFC2396, Section 5.2 (3)
2514            if other.scheme is not None:
2515                return other
2516            newurl.scheme = self.scheme
2517            newurl.query = other.query
2518            newurl.frag = other.frag
2519            # RFC2396, Section 5.2 (4)
2520            if other.authority is None:
2521                newurl.authority = self.authority
2522                # RFC2396, Section 5.2 (5) & (6) (a) (b)
2523                newurl._path = self._path/other._path
2524            else:
2525                newurl.authority = other.authority
2526                newurl._path = other._path.clone()
2527            return newurl
2528        elif isinstance(other, (list, tuple)): # this makes path/list possible
2529            return other.__class__(self/path for path in other)
2530        else: # this makes path/generator possible
2531            return (self/path for path in other)
2532
2533    def __rdiv__(self, other):
2534        """
2535        Right hand version of :meth:`__div__`. This supports lists and iterables
2536        as the left hand side too.
2537        """
2538        if isinstance(other, basestring):
2539            other = URL(other)
2540        if isinstance(other, URL):
2541            return other/self
2542        elif isinstance(other, (list, tuple)):
2543            return other.__class__(item/self for item in other)
2544        else:
2545            return (item/self for item in other)
2546
2547    def relative(self, baseurl):
2548        """
2549        Return an relative :class:`URL` :var:`rel` such that
2550        ``baseurl/rel == self``, i.e. this is the inverse operation of
2551        :meth:`__div__`.
2552
2553        If :var:`self` is relative, has a different :prop:`scheme` or
2554        :prop:`authority` than :var:`baseurl` or a non-hierarchical scheme, an
2555        identical copy of :var:`self` will be returned.
2556        """
2557        # if :var:`self` is relative don't do anything
2558        if self.scheme is None:
2559            return URL(self)
2560        # javascript etc.
2561        if not self.reg.usehierarchy:
2562            return URL(self)
2563        baseurl = URL(baseurl) # clone/coerce
2564        # only calculate a new URL if to the same server, else use the original
2565        if self.scheme != baseurl.scheme or self.authority != baseurl.authority:
2566            return URL(self)
2567        newurl = URL(self) # clone
2568        del newurl.scheme
2569        del newurl.authority
2570        selfpath_segments = _normalizepath(self._path.segments)
2571        basepath_segments = _normalizepath(baseurl._path.segments)
2572        while len(selfpath_segments)>1 and len(basepath_segments)>1 and selfpath_segments[0]==basepath_segments[0]:
2573            del selfpath_segments[0]
2574            del basepath_segments[0]
2575        # does the URL go to the same file?
2576        if selfpath_segments==basepath_segments and self.query==baseurl.query:
2577            # only return the frag
2578            del newurl.path
2579            del newurl.query
2580        else:
2581            # build a path from one file to the other
2582            selfpath_segments[:0] = [u".."]*(len(basepath_segments)-1)
2583            if not len(selfpath_segments) or selfpath_segments==[u""]:
2584                selfpath_segments = [u".", u""]
2585            newurl._path.segments = selfpath_segments
2586            newurl._path = self.path.relative(baseurl.path)
2587        newurl._path.isabs = False
2588        return newurl
2589
2590    def __str__(self):
2591        return self.url
2592
2593    def __unicode__(self):
2594        return self.url
2595
2596    def __repr__(self):
2597        return "URL({0!r})".format(self.url)
2598
2599    def __nonzero__(self):
2600        """
2601        Return whether the :class:`URL` is not empty, i.e. whether it is not the
2602        :class:`URL` referring to the start of the current document.
2603        """
2604        return self.url != ""
2605
2606    def __eq__(self, other):
2607        """
2608        Return whether two :class:`URL` objects are equal. Note that only
2609        properties relevant for the current scheme will be compared.
2610        """
2611        if self.__class__ != other.__class__:
2612            return False
2613        if self.scheme != other.scheme:
2614            return False
2615        if self.reg.usehierarchy:
2616            if self.reg.useserver:
2617                selfport = self.port or self.reg.defaultport
2618                otherport = other.port or other.reg.defaultport
2619                if self.userinfo != other.userinfo or self.host != other.host or selfport != otherport:
2620                    return False
2621            else:
2622                if self.reg_name != other.reg_name:
2623                    return False
2624            if self._path != other._path:
2625                return False
2626        else:
2627            if self.opaque_part != other.opaque_part:
2628                return False
2629        # Use canonical version of (i.e. sorted names and values)
2630        if self.query != other.query:
2631            return False
2632        if self.frag != other.frag:
2633            return False
2634        return True
2635
2636    def __ne__(self, other):
2637        """
2638        Return whether two :class:`URL` objects are *not* equal.
2639        """
2640        return not self==other
2641
2642    def __hash__(self):
2643        """
2644        Return a hash value for :var:`self`, to be able to use :class:`URL`
2645        objects as dictionary keys. You must be careful not to modify an
2646        :class:`URL` as soon as you use it as a dictionary key.
2647        """
2648        res = hash(self.scheme)
2649        if self.reg.usehierarchy:
2650            if self.reg.useserver:
2651                res ^= hash(self.userinfo)
2652                res ^= hash(self.host)
2653                res ^= hash(self.port or self.reg.defaultport)
2654            else:
2655                res ^= hash(self.reg_name)
2656            res ^= hash(self._path)
2657        else:
2658            res ^= hash(self.opaque_part)
2659        res ^= hash(self.query)
2660        res ^= hash(self.frag)
2661        return res
2662
2663    def abs(self, scheme=-1):
2664        """
2665        Return an absolute version of :var:`self` (works only for local URLs).
2666
2667        If the argument :var:`scheme` is specified, it will be used for the
2668        resulting URL otherwise the result will have the same scheme as
2669        :var:`self`.
2670        """
2671        self._checklocal()
2672        new = self.clone()
2673        new.path = self.path.abs()
2674        if scheme != -1:
2675            new.scheme = scheme
2676        return new
2677
2678    def real(self, scheme=-1):
2679        """
2680        Return the canonical version of :var:`self`, eliminating all symbolic
2681        links (works only for local URLs).
2682
2683        If the argument :var:`scheme` is specified, it will be used for the
2684        resulting URL otherwise the result will have the same scheme as
2685        :var:`self`.
2686        """
2687        self._checklocal()
2688        new = self.clone()
2689        new.path = self.path.real()
2690        if scheme != -1:
2691            new.scheme = scheme
2692        return new
2693
2694    def islocal(self):
2695        """
2696        Return whether :var:`self` refers to a local file, i.e. whether
2697        :var:`self` is a relative :class:`URL` or the scheme is ``root`` or
2698        ``file``).
2699        """
2700        return self.reg.islocal
2701
2702    def _checklocal(self):
2703        if not self.islocal():
2704            raise ValueError("URL {0!r} is not local".format(self))
2705
2706    def local(self):
2707        """
2708        Return :var:`self` as a local filename (which will only works if
2709        :var:`self` is local (see :meth:`islocal`).
2710        """
2711        self._checklocal()
2712        return self.path.local()
2713
2714    def _connect(self, context=None, **kwargs):
2715        return self.reg._connect(self, context=context, **kwargs)
2716
2717    def connect(self, context=None, **kwargs):
2718        """
2719        Return a :class:`Connection` object for accessing and modifying the
2720        metadata of :var:`self`.
2721
2722        Whether you get a new connection object, or an existing one depends on
2723        the scheme, the URL itself, and the context passed in (as the
2724        :var:`context` argument).
2725        """
2726        return self._connect(context, **kwargs)[0]
2727
2728    def open(self, mode="rb", context=None, *args, **kwargs):
2729        """
2730        Open :var:`self` for reading or writing. :meth:`open` returns a
2731        :class:`Resource` object.
2732
2733        Which additional parameters are supported depends on the actual resource
2734        created. Some common parameters are:
2735
2736            :var:`mode` (supported by all resources)
2737                A string indicating how the file is to be opened (just like the
2738                mode argument for the builtin :func:`open`; e.g. ``"rb"`` or
2739                ``"wb"``).
2740
2741            :var:`context` (supported by all resources)
2742                :meth:`open` needs a :class:`Connection` for this URL which it gets
2743                from a :class:`Context` object.
2744
2745            :var:`headers`
2746                Additional headers to use for an HTTP request.
2747
2748            :var:`data`
2749                Request body to use for an HTTP POST request.
2750
2751            :var:`remotepython`
2752                Name of the Python interpreter to use on the remote side
2753                (used by ``ssh`` URLs)
2754
2755            :var:`nice`
2756                Nice level for the remove python (used by ``ssh`` URLs)
2757        """
2758        (connection, kwargs) = self._connect(context=context, **kwargs)
2759        return connection.open(self, mode, *args, **kwargs)
2760
2761    def openread(self, context=None, *args, **kwargs):
2762        return self.open("rb", context, *args, **kwargs)
2763
2764    def openwrite(self, context=None, *args, **kwargs):
2765        return self.open("wb", context, *args, **kwargs)
2766
2767    def __getattr__(self, name):
2768        """
2769        :meth:`__getattr__` forwards every unresolved attribute access to the
2770        appropriate connection. This makes it possible to call :class:`Connection`
2771        methods directly on :class:`URL` objects::
2772
2773            >>> from ll import url
2774            >>> u = url.URL("file:README")
2775            >>> u.size()
2776            1584L
2777
2778        instead of::
2779
2780            >>> from ll import url
2781            >>> u = url.URL("file:README")
2782            >>> u.connect().size(u)
2783            1584L
2784        """
2785        def realattr(*args, **kwargs):
2786            try:
2787                context = kwargs["context"]
2788            except KeyError:
2789                context = None
2790            else:
2791                kwargs = kwargs.copy()
2792                del kwargs["context"]
2793            (connection, kwargs) = self._connect(context=context, **kwargs)
2794            return getattr(connection, name)(self, *args, **kwargs)
2795        return realattr
2796
2797    def __iter__(self):
2798        try:
2799            isdir = self.isdir()
2800        except AttributeError:
2801            isdir = False
2802        if isdir:
2803            return iter(self/self.listdir())
2804        else:
2805            return iter(self.open())
2806
2807    def __xrepr__(self, mode="default"):
2808        if mode == "cell":
2809            yield (astyle.style_url, str(self))
2810        else:
2811            yield (astyle.style_url, repr(self))
2812
2813
2814warnings.filterwarnings("always", module="url")
Note: See TracBrowser for help on using the browser.