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