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