Fixing some load/save issues with filebackend
[doneit] / src / toolbox.py
1 import sys
2 import StringIO
3 import urllib
4 import urlparse
5 from xml.dom import minidom
6 import datetime
7
8
9 class NonExistent(object):
10         pass
11
12
13 class Optional(object):
14         """
15         Taglines:
16         Even you don't have to worry about knowing when to perform None checks
17         When the NULL object pattern just isn't good enough
18
19         >>> a = Optional()
20         >>> a.is_good()
21         False
22         >>> a.get_nothrow("Blacksheep")
23         'Blacksheep'
24         >>> a.set("Lamb")
25         >>> a.is_good()
26         True
27         >>> a.get_nothrow("Blacksheep"), a.get(), a()
28         ('Lamb', 'Lamb', 'Lamb')
29         >>> a.clear()
30         >>> a.is_good()
31         False
32         >>> a.get_nothrow("Blacksheep")
33         'Blacksheep'
34         >>>
35         >>> b = Optional("Lamb")
36         >>> a.set("Lamb")
37         >>> a.is_good()
38         True
39         >>> a.get_nothrow("Blacksheep"), a.get(), a()
40         ('Lamb', 'Lamb', 'Lamb')
41         >>> a.clear()
42         >>> a.is_good()
43         False
44         >>> a.get_nothrow("Blacksheep")
45         'Blacksheep'
46         """
47
48         def __init__(self, value = NonExistent):
49                 self._value = value
50
51         def set(self, value):
52                 self._value = value
53
54         def clear(self):
55                 self._value = NonExistent
56
57         def is_good(self):
58                 return self._value is not NonExistent
59
60         def get_nothrow(self, default = None):
61                 return self._value if self.is_good() else default
62
63         def get(self):
64                 if not self.is_good():
65                         raise ReferenceError("Optional not initialized")
66                 return self._value
67
68         def __call__(self):
69                 # Implemented to imitate weakref
70                 return self.get()
71
72         def __str__(self):
73                 return str(self.get_nothrow(""))
74
75         def __repr__(self):
76                 return "<Optional at %x; to %r>" % (
77                         id(self), self.get_nothrow("Nothing")
78                 )
79
80         def __not__(self):
81                 return not self.is_good()
82
83         def __eq__(self, rhs):
84                 return self._value == rhs._value
85
86         def __ne__(self, rhs):
87                 return self._value != rhs._value
88
89         def __lt__(self, rhs):
90                 return self._value < rhs._value
91
92         def __le__(self, rhs):
93                 return self._value <= rhs._value
94
95         def __gt__(self, rhs):
96                 return self._value > rhs._value
97
98         def __ge__(self, rhs):
99                 return self._value >= rhs._value
100
101
102 def open_anything(source, alternative=None):
103         """URI, filename, or string --> stream
104
105         This function lets you define parsers that take any input source
106         (URL, pathname to local or network file, or actual data as a string)
107         and deal with it in a uniform manner.  Returned object is guaranteed
108         to have all the basic stdio read methods (read, readline, readlines).
109         Just .close() the object when you're done with it.
110         """
111         if hasattr(source, "read"):
112                 return source
113
114         if source == '-':
115                 return sys.stdin
116
117         # try to open with urllib (if source is http, ftp, or file URL)
118         try:
119                 return urllib.urlopen(source)
120         except (IOError, OSError):
121                 ##pass
122                 print "ERROR with URL ("+source+")!\n"
123
124         # try to open with native open function (if source is pathname)
125         try:
126                 return open(source)
127         except (IOError, OSError):
128                 ##pass
129                 print "ERROR with file!\n"
130
131         # treat source as string
132         if alternative == None:
133                 print 'LAST RESORT.  String is "'+source+'"\n'
134                 return StringIO.StringIO(str(source))
135         else:
136                 print 'LAST RESORT.  String is "'+alternative+'"\n'
137                 return StringIO.StringIO(str(alternative))
138
139
140 def load_xml(source, alternative=None):
141         """load XML input source, return parsed XML document
142
143         - a URL of a remote XML file ("http://diveintopython.org/kant.xml")
144         - a filename of a local XML file ("~/diveintopython/common/py/kant.xml")
145         - standard input ("-")
146         - the actual XML document, as a string
147         """
148         sock = open_anything(source, alternative)
149         try:
150                 xmldoc = minidom.parse(sock).documentElement
151         except (IOError, OSError):
152                 print "ERROR with data"
153                 sock.close()
154                 sock = open_anything('<response method="getProjects"><project projName="ERROR!"/></response>')
155                 xmldoc = minidom.parse(sock).documentElement
156         sock.close()
157         return xmldoc
158
159
160 def abbreviate(text, expectedLen):
161         singleLine = " ".join(text.split("\n"))
162         lineLen = len(singleLine)
163         if lineLen <= expectedLen:
164                 return singleLine
165
166         abbrev = "..."
167
168         leftLen = expectedLen // 2 - 1
169         rightLen = max(expectedLen - leftLen - len(abbrev) + 1, 1)
170
171         abbrevText =  singleLine[0:leftLen] + abbrev + singleLine[-rightLen:-1]
172         assert len(abbrevText) <= expectedLen, "Too long: '%s'" % abbrevText
173         return abbrevText
174
175
176 def abbreviate_url(url, domainLength, pathLength):
177         urlParts = urlparse.urlparse(url)
178
179         netloc = urlParts.netloc
180         path = urlParts.path
181
182         pathLength += max(domainLength - len(netloc), 0)
183         domainLength += max(pathLength - len(path), 0)
184
185         netloc = abbreviate(netloc, domainLength)
186         path = abbreviate(path, pathLength)
187         return netloc + path
188
189
190 def is_same_year(targetDate, todaysDate = datetime.datetime.today()):
191         return targetDate.year == todaysDate.year
192
193
194 def is_same_month(targetDate, todaysDate = datetime.datetime.today()):
195         return targetDate.month == todaysDate.month
196
197
198 def is_same_day(targetDate, todaysDate = datetime.datetime.today()):
199         return targetDate.day == todaysDate.day
200
201
202 def to_fuzzy_date(targetDate, todaysDate = datetime.datetime.today()):
203         """
204         Conert a date/time/datetime object to a fuzzy date
205
206         >>> todaysDate = datetime.date(2009, 4, 16)
207         >>> to_fuzzy_date(datetime.date(1, 4, 6), todaysDate)
208         'Forever ago'
209         >>> to_fuzzy_date(datetime.date(2008, 4, 13), todaysDate)
210         'Last year'
211         >>> to_fuzzy_date(datetime.date(2009, 4, 13), todaysDate)
212         'Last Monday'
213         >>> to_fuzzy_date(datetime.date(2009, 4, 20), todaysDate)
214         'This Monday'
215         >>> to_fuzzy_date(datetime.date(2010, 4, 13), todaysDate)
216         'Next year'
217         >>> to_fuzzy_date(datetime.date(2012, 12, 12), todaysDate)
218         'Forever from now'
219         """
220         delta = targetDate - todaysDate
221         days = abs(delta.days)
222         noDifference = datetime.timedelta()
223         isFuture = noDifference < delta
224         directionBy1 = "Next" if isFuture else "Last"
225         directionByN = "Later" if isFuture else "Earlier"
226
227         yearDelta = abs(targetDate.year - todaysDate.year)
228         if 1 < yearDelta:
229                 directionByInf = "from now" if isFuture else "ago"
230                 return "Forever %s" % directionByInf
231         elif 1 == yearDelta:
232                 return "%s year" % directionBy1
233
234         monthDelta = abs(targetDate.month - todaysDate.month)
235         if 1 < monthDelta:
236                 return "%s this year" % directionByN
237         elif 1 == monthDelta:
238                 return "%s month" % directionBy1
239
240         dayDelta = abs(targetDate.day - todaysDate.day)
241         if 14 < dayDelta:
242                 return "%s this month" % directionByN
243         elif 1 < dayDelta:
244                 directionInWeek = "This" if isFuture else "Last"
245                 days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
246                 return "%s %s" % (directionInWeek, days[targetDate.weekday()])
247         elif 1 == dayDelta:
248                 return "%s day" % directionBy1
249         else:
250                 return "Today"