#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
   URL class

   @copyright: 2006 Thomas Waldmann
   @license: GNU GPL, see COPYING for details
"""
import posixpath, urllib, urlparse, cgi

class URL(object):
    """ represents an Uniform Resource Locator """
    url_coding = 'utf-8' # we use this for en/decoding URLs
    default_ports = { 'ftp': 21, 'http': 80, 'https': 443, }

    def __init__(self, url=None, **kw):
        """create URL object

           If url is given, it can be:
           * str (will be parsed and also stored "as is")
           * unicode (will be encoded, then: see str)
           * another URL object
           
           If url is given, it will set the defaults. Those defaults
           can be modified by specifying a different value as kw argument.

           If url is not given, you can also give all pieces needed to
           make one by kw arguments:
           * scheme
           * server
           * alternatively: host and maybe port
           * path
           * fragment (e.g. "anchorid")
           * and named query args 
        """
        # some defaults to start with:
        self._url = None
        self._scheme = 'http'
        self._server = ''
        self._host = ''
        self._port = None
        self._path = ''
        self._query = ''
        self._querydict = {}
        self._fragment = ''
        if isinstance(url, URL):
            # there is an easier way for this:
            self._scheme = url.scheme
            self._server = url.server
            #self._host = url.host
            #self._port = url.port
            self._path = url.path
            self._query = url.query
            self._querydict = url.querydict
            self._fragment = url.fragment
        elif isinstance(url, str):
            self.url = url
        elif isinstance(url, unicode):
            self.url = url.encode(self.url_coding)
        elif url is None:
            pass
        else:
            raise AttributeError, "unexpected argument type for url_default"
        
        # now update what we have with the kw arg stuff:
        if kw:
            self._update(**kw)

    def _update(self, **kw):
        """ update what we have from kw, care for unicode encoding """
        for key in ('scheme', 'server', 'host', 'port', 'path', 'fragment',):
            if kw.has_key(key):
                value = kw[key]
                del kw[key]
                if isinstance(value, unicode):
                    value = value.encode(self.url_coding)
                getattr(self, '_set_%s' % key)(value)
        # add what's left to the query dict and remove query arguments that are None:
        qd = self.querydict
        for key, value in kw.items():
            if value is None:
                try:
                    del qd[key]
                except KeyError:
                    pass
            elif isinstance(value, unicode):
                qd[key] = value.encode(self.url_coding)
            else:
                qd[key] = str(value)
        self.querydict = qd
    
    def _split(self, url):
        """ wrapper around urlparse.urlsplit """
        self._scheme, self._server, self._path, self._query, self._fragment = urlparse.urlsplit(url)
        # we also split the server part into host and port:
        host_port = self._server.split(':', 1) # rsplit would be better, but is 2.4 only
        self._host = host_port[0]
        try:
            self._port = int(host_port[1])
        except (ValueError, IndexError), err:
            self._port = URL.default_ports.get(self._scheme)

    def __str__(self):
        return self.url

    def __repr__(self):
        return '<URL "%s">' % self
    
    def _join_host_port(self):
        """ join host and port components to make a server string """
        port = self._port
        def_port = URL.default_ports.get(self._scheme)
        if port == def_port or port is None:
            self._server = self._host
        else:
            self._server = "%s:%d" % (self._host, port)
    
    def _get_scheme(self):
        if not hasattr(self, '_scheme'):
            self._split(self._url)
        return self._scheme
    def _set_scheme(self, v):
        if v != self._scheme:
            self._scheme = v
            del self.url
    def _del_scheme(self):
        try:
            del self._scheme
        except AttributeError:
            pass

    def _get_server(self):
        if not hasattr(self, '_server'):
            self._split(self._url)
        return self._server
    def _set_server(self, v):
        if v != self._server:
            self._server = v
            del self.host
            del self.port
            del self.url
    def _del_server(self):
        try:
            del self._server
        except AttributeError:
            pass

    def _get_host(self):
        if not hasattr(self, '_host'):
            self._split(self._url)
        return self._host
    def _set_host(self, v):
        if v != self._host:
            self._host = v
            self._join_host_port()
            del self.url
    def _del_host(self):
        try:
            del self._host
        except AttributeError:
            pass

    def _get_port(self):
        if not hasattr(self, '_port'):
            self._split(self._url)
        return self._port
    def _set_port(self, v):
        if v != self._port:
            self._port = v
            self._join_host_port()
            del self.url
    def _del_port(self):
        try:
            del self._port
        except AttributeError:
            pass

    def _get_path(self):
        if not hasattr(self, '_path'):
            self._split(self._url)
        return self._path
    def _set_path(self, v):
        if v != self._path:
            self._path = v
            del self.url
    def _del_path(self):
        try:
            del self._path
        except AttributeError:
            pass

    def _get_query(self):
        if not hasattr(self, '_query'):
            self._split(self._url)
        return self._query
    def _set_query(self, v):
        if v != self._query:
            self._query = v
            del self.querydict
            del self.url
    def _del_query(self):
        try:
            del self._query
        except AttributeError:
            pass

    def _get_querydict(self):
        if not hasattr(self, '_querydict'):
            self._querydict = cgi.parse_qs(self.query)
        return self._querydict
    def _set_querydict(self, v):
        self._querydict = v
        self._query = urllib.urlencode(self._querydict)
        del self.url
    def _del_querydict(self):
        try:
            del self._querydict
        except AttributeError:
            pass
    
    def _get_fragment(self):
        if not hasattr(self, '_fragment'):
            self._split(self._url)
        return self._fragment
    def _set_fragment(self, v):
        if v != self._fragment:
            self._fragment = v
            del self.url
    def _del_fragment(self):
        try:
            del self._fragment
        except AttributeError:
            pass

    def _get_url(self):
        if not hasattr(self, '_url') or self._url is None:
            self._url = urlparse.urlunsplit((self.scheme, self.server, self.path, self.query, self.fragment))
        return self._url
    def _set_url(self, v):
        if v != self._url:
            self._url = v
            del self.scheme
            del self.host
            del self.port
            del self.server
            del self.path
            del self.query
            del self.querydict
            del self.fragment
    def _del_url(self):
        try:
            del self._url
        except AttributeError:
            pass

    def _get_local(self):
        if not hasattr(self, '_local'):
            self._local = urlparse.urlunsplit(('', '', self.path, self.query, self.fragment))
        return self._local
    
    scheme = property(_get_scheme, _set_scheme, _del_scheme)
    server = property(_get_server, _set_server, _del_server)
    host = property(_get_host, _set_host, _del_host)
    port = property(_get_port, _set_port, _del_port)
    path = property(_get_path, _set_path, _del_path)
    query = property(_get_query, _set_query, _del_query)
    querydict = property(_get_querydict, _set_querydict, _del_querydict)
    fragment = property(_get_fragment, _set_fragment, _del_fragment)
    url = property(_get_url, _set_url, _del_url)
    local = property(_get_local)

if __name__ == '__main__':
    curr = URL('http://www.google.de/asdfasdf/asdfasdf?asdfasdf=adsfsadf/')
    assert str(curr) == 'http://www.google.de/asdfasdf/asdfasdf?asdfasdf=adsfsadf/'
    assert curr.scheme == 'http'
    assert curr.server == 'www.google.de'
    assert (curr.host, curr.port) == ('www.google.de', 80)
    assert curr.path == '/asdfasdf/asdfasdf'
    assert curr.query == 'asdfasdf=adsfsadf/'
    assert curr.fragment == ''
    print "splitting assertions successful"

    curr = URL(scheme='http', server='wikiwikiweb.de', path='/FrontPage', action="diff", rev1=2, rev2=3, fragment='line-123')
    assert str(curr) == 'http://wikiwikiweb.de/FrontPage?action=diff&rev1=2&rev2=3#line-123'
    assert (curr.host, curr.port) == ('wikiwikiweb.de', 80)
    print "joining assertions successful"
   
    curr = URL("http://wikiwikiweb.de/FrontPage")
    u = URL(curr, scheme="https")
    assert str(u) == 'https://wikiwikiweb.de/FrontPage'
    u = URL(curr, action="raw", rev=42)
    assert str(u) == 'http://wikiwikiweb.de/FrontPage?action=raw&rev=42'
    u = URL(curr, action="AttachFile", do="get", file=u"Übler Dübel.doc")
    assert str(u) == 'http://wikiwikiweb.de/FrontPage?action=AttachFile&do=get&rev=42&file=%C3%9Cbler+D%C3%BCbel.doc'
    u = URL(u, action=None, do=None, rev=None, file=None)
    assert str(u) == 'http://wikiwikiweb.de/FrontPage'
    print "updating assertions successful"
    
    #these still fail:
    u = URL(curr, port=42, path="/wiki/img/moinmoin.png")
    print u
    assert str(u) == 'http://wikiwikiweb.de:42/wiki/img/moinmoin.png'

