780750b6fca4b82a26fc3ebe54c00d27d2275eec
[frenchie/icalparse.git] / icalparse.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2010 James French <[email protected]>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22
23 import sys
24 import urlparse
25 import os
26
27
28 class InvalidICS(Exception): pass
29 class notJoined(Exception): pass
30 class IncompleteICS(InvalidICS): pass
31
32
33 def lineJoiner(oldcal):
34         '''Takes a string containing a calendar and returns an array of its lines'''
35
36         if not oldcal[0:15] == 'BEGIN:VCALENDAR':
37                 raise InvalidICS, "Does not appear to be a valid ICS file"
38
39         if not 'END:VCALENDAR' in oldcal[-15:-1]:
40                 raise IncompleteICS, "File appears to be incomplete"
41
42         if list(oldcal) == oldcal:
43                 oldcal = '\r\n'.join(oldcal)
44
45         oldcal = oldcal.replace('\r\n ', '').replace('\r\n\t','')
46         return oldcal.strip().split('\r\n')
47
48
49 def lineFolder(oldcal, length=75):
50         '''Folds content lines to a specified length, returns a list'''
51
52         if length > 75:
53                 sys.stderr.write('WARN: lines > 75 octets are not RFC compliant\n')
54
55         cal = []
56         sl = length - 1
57
58         for line in oldcal:
59                 # Line fits inside length, do nothing
60                 if len(line.rstrip()) <= length:
61                         cal.append(line)
62                 else:
63                         brokenline = [line[0:length] + '\r\n']
64                         ll = length
65                         while ll < len(line.rstrip('\r\n')) + 1:
66                                 brokenline.append(' ' + line[ll:sl+ll].rstrip('\r\n') + '\r\n')
67                                 ll += sl
68                         cal += brokenline
69
70         return cal
71
72
73 def getContent(url='',stdin=False):
74         '''Generic content retriever, DO NOT use this function in a CGI script as
75         it can read from the local disk (which you probably don't want it to).
76         '''
77
78         # Special case, if this is a HTTP url, return the data from it using
79         # the HTTP functions which attempt to play a bit nicer.
80         parsedURL = urlparse.urlparse(url)
81         if 'http' in parsedURL[0]: return getHTTPContent(url)
82
83         if stdin:
84                 content = sys.stdin.read()
85                 return content
86
87         if not parsedURL[0]:
88                 try: content = open(os.path.abspath(url),'r').read()
89                 except (IOError, OSError), e:
90                         sys.stderr.write('%s\n'%e)
91                         sys.exit(1)
92                 return content
93
94         # If we've survived, use python's generic URL opening library to handle it
95         import urllib2
96         try:
97                 res = urllib2.urlopen(url)
98                 content = res.read()
99                 res.close()
100         except (urllib2.URLError, ValueError), e:
101                 sys.stderr.write('%s\n'%e)
102                 sys.exit(1)
103         return content
104
105
106 def getHTTPContent(url='',cache='.httplib2-cache'):
107         '''This function attempts to play nice when retrieving content from HTTP
108         services. It's what you should use in a CGI script. It will (by default)
109         slurp the first 20 bytes of the file and check that we are indeed looking
110         at an ICS file before going for broke.'''
111
112         try:
113                 import httplib2
114         except ImportError:
115                 import urllib2
116
117         if not url: return ''
118
119         if 'httplib2' in sys.modules:
120                 try: h = httplib2.Http('.httplib2-cache')
121                 except OSError: h = httplib2.Http()
122         else: h = False
123
124         try:
125                 if h: content = h.request(url)[1]
126                 return content
127         except ValueError, e:
128                 sys.stderr.write('%s\n'%e)
129                 sys.exit(1)
130
131         try:
132                 content = urllib2.urlopen(url).read()
133                 return content
134         except urllib2.URLError, e:
135                 sys.stderr.write('%s\n'%e)
136                 sys.exit(1)
137
138         return ''
139
140 if __name__ == '__main__':
141         from optparse import OptionParser
142         # If the user passed us a 'stdin' argument, we'll go with that,
143         # otherwise we'll try for a url opener
144
145         parser = OptionParser('usage: %prog [options] url')
146         parser.add_option('-s', '--stdin', action='store_true', dest='stdin',
147                 default=False, help='Take a calendar from standard input')
148         parser.add_option('-o', '--output', dest='outfile', default='',
149                 help='Specify output file (defaults to standard output)')
150
151         (options, args) = parser.parse_args()
152
153         if not args and not options.stdin:
154                 parser.print_usage()
155                 sys.exit(0)
156         elif not options.stdin:
157                 url = args[0]
158         else:
159                 url = ''
160
161         content = getContent(url, options.stdin)
162         cal = lineJoiner(content)
163         print cal

UCC git Repository :: git.ucc.asn.au