Fixing some load/save issues with filebackend
[doneit] / src / toolbox.py
index e6dfcbf..92cfeff 100644 (file)
 import sys
 import StringIO
 import urllib
+import urlparse
 from xml.dom import minidom
 import datetime
 
 
+class NonExistent(object):
+       pass
+
+
+class Optional(object):
+       """
+       Taglines:
+       Even you don't have to worry about knowing when to perform None checks
+       When the NULL object pattern just isn't good enough
+
+       >>> a = Optional()
+       >>> a.is_good()
+       False
+       >>> a.get_nothrow("Blacksheep")
+       'Blacksheep'
+       >>> a.set("Lamb")
+       >>> a.is_good()
+       True
+       >>> a.get_nothrow("Blacksheep"), a.get(), a()
+       ('Lamb', 'Lamb', 'Lamb')
+       >>> a.clear()
+       >>> a.is_good()
+       False
+       >>> a.get_nothrow("Blacksheep")
+       'Blacksheep'
+       >>>
+       >>> b = Optional("Lamb")
+       >>> a.set("Lamb")
+       >>> a.is_good()
+       True
+       >>> a.get_nothrow("Blacksheep"), a.get(), a()
+       ('Lamb', 'Lamb', 'Lamb')
+       >>> a.clear()
+       >>> a.is_good()
+       False
+       >>> a.get_nothrow("Blacksheep")
+       'Blacksheep'
+       """
+
+       def __init__(self, value = NonExistent):
+               self._value = value
+
+       def set(self, value):
+               self._value = value
+
+       def clear(self):
+               self._value = NonExistent
+
+       def is_good(self):
+               return self._value is not NonExistent
+
+       def get_nothrow(self, default = None):
+               return self._value if self.is_good() else default
+
+       def get(self):
+               if not self.is_good():
+                       raise ReferenceError("Optional not initialized")
+               return self._value
+
+       def __call__(self):
+               # Implemented to imitate weakref
+               return self.get()
+
+       def __str__(self):
+               return str(self.get_nothrow(""))
+
+       def __repr__(self):
+               return "<Optional at %x; to %r>" % (
+                       id(self), self.get_nothrow("Nothing")
+               )
+
+       def __not__(self):
+               return not self.is_good()
+
+       def __eq__(self, rhs):
+               return self._value == rhs._value
+
+       def __ne__(self, rhs):
+               return self._value != rhs._value
+
+       def __lt__(self, rhs):
+               return self._value < rhs._value
+
+       def __le__(self, rhs):
+               return self._value <= rhs._value
+
+       def __gt__(self, rhs):
+               return self._value > rhs._value
+
+       def __ge__(self, rhs):
+               return self._value >= rhs._value
+
+
 def open_anything(source, alternative=None):
        """URI, filename, or string --> stream
 
@@ -63,32 +157,94 @@ def load_xml(source, alternative=None):
        return xmldoc
 
 
+def abbreviate(text, expectedLen):
+       singleLine = " ".join(text.split("\n"))
+       lineLen = len(singleLine)
+       if lineLen <= expectedLen:
+               return singleLine
+
+       abbrev = "..."
+
+       leftLen = expectedLen // 2 - 1
+       rightLen = max(expectedLen - leftLen - len(abbrev) + 1, 1)
+
+       abbrevText =  singleLine[0:leftLen] + abbrev + singleLine[-rightLen:-1]
+       assert len(abbrevText) <= expectedLen, "Too long: '%s'" % abbrevText
+       return abbrevText
+
+
+def abbreviate_url(url, domainLength, pathLength):
+       urlParts = urlparse.urlparse(url)
+
+       netloc = urlParts.netloc
+       path = urlParts.path
+
+       pathLength += max(domainLength - len(netloc), 0)
+       domainLength += max(pathLength - len(path), 0)
+
+       netloc = abbreviate(netloc, domainLength)
+       path = abbreviate(path, pathLength)
+       return netloc + path
+
+
+def is_same_year(targetDate, todaysDate = datetime.datetime.today()):
+       return targetDate.year == todaysDate.year
+
+
+def is_same_month(targetDate, todaysDate = datetime.datetime.today()):
+       return targetDate.month == todaysDate.month
+
+
+def is_same_day(targetDate, todaysDate = datetime.datetime.today()):
+       return targetDate.day == todaysDate.day
+
+
 def to_fuzzy_date(targetDate, todaysDate = datetime.datetime.today()):
        """
        Conert a date/time/datetime object to a fuzzy date
 
-       @bug Not perfect, but good enough for now
+       >>> todaysDate = datetime.date(2009, 4, 16)
+       >>> to_fuzzy_date(datetime.date(1, 4, 6), todaysDate)
+       'Forever ago'
+       >>> to_fuzzy_date(datetime.date(2008, 4, 13), todaysDate)
+       'Last year'
+       >>> to_fuzzy_date(datetime.date(2009, 4, 13), todaysDate)
+       'Last Monday'
+       >>> to_fuzzy_date(datetime.date(2009, 4, 20), todaysDate)
+       'This Monday'
+       >>> to_fuzzy_date(datetime.date(2010, 4, 13), todaysDate)
+       'Next year'
+       >>> to_fuzzy_date(datetime.date(2012, 12, 12), todaysDate)
+       'Forever from now'
        """
        delta = targetDate - todaysDate
        days = abs(delta.days)
-       directionBy1 = "Next" if 0 < delta.days else "Last"
-       directionByN = "Later" if 0 < delta.days else "Earlier"
-       directionByInf = "from now" if 0 < delta.days else "ago"
-       if 2*365 < days:
+       noDifference = datetime.timedelta()
+       isFuture = noDifference < delta
+       directionBy1 = "Next" if isFuture else "Last"
+       directionByN = "Later" if isFuture else "Earlier"
+
+       yearDelta = abs(targetDate.year - todaysDate.year)
+       if 1 < yearDelta:
+               directionByInf = "from now" if isFuture else "ago"
                return "Forever %s" % directionByInf
-       elif 365 < days:
+       elif 1 == yearDelta:
                return "%s year" % directionBy1
-       elif 2*30 < days:
+
+       monthDelta = abs(targetDate.month - todaysDate.month)
+       if 1 < monthDelta:
                return "%s this year" % directionByN
-       elif 30 < days:
+       elif 1 == monthDelta:
                return "%s month" % directionBy1
-       elif 14 < days:
+
+       dayDelta = abs(targetDate.day - todaysDate.day)
+       if 14 < dayDelta:
                return "%s this month" % directionByN
-       elif 7 < days:
-               return "%s week" % directionBy1
-       elif 2 < days:
-               return "%s this week" % directionByN
-       elif 1 < days:
-               return "%s day" % directionByN
+       elif 1 < dayDelta:
+               directionInWeek = "This" if isFuture else "Last"
+               days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
+               return "%s %s" % (directionInWeek, days[targetDate.weekday()])
+       elif 1 == dayDelta:
+               return "%s day" % directionBy1
        else:
                return "Today"