', True),
- 'vmjg59': ('Virtual Matthew Garrett', 'http://www.rjek.com/vmjg59.cgi', r'
(?P.*?)', True),
- 'shakespeare': ('Shakespeare quotes', 'http://www.pangloss.com/seidel/Shaker/', r'(?P.*?)', False),
- 'lugradio': ('Lugradio facts', 'http://planet.lugradio.org/facts/', r'\s*(?P.*?) ', False),
- 'bofh': ('BOFH excuses', '%s/bofh.txt' % os.path.split(os.path.abspath(__file__))[0], 'BOFH Excuse #%d: ', False),
- '42': ('HHGTTG quotes', '%s/42.txt' % os.path.split(os.path.abspath(__file__))[0], '', False),
- 'magic8ball': ('The magic 8ball', '%s/ball.txt' % os.path.split(os.path.abspath(__file__))[0], '', False),
- 'ferengi': ('Ferengi rules of acquisition', '%s/ferengi.txt' % os.path.split(os.path.abspath(__file__))[0], 'Ferengi rule of acquisition ', False),
-}
-data = {}
-for m in mess.keys():
- if mess[m][1].startswith('http'):
- mess[m] = (mess[m][0], mess[m][1],re.compile(mess[m][2], re.I|re.DOTALL), mess[m][3])
- else:
- fd = open(mess[m][1])
- data[mess[m][1]] = [x.strip() for x in fd.readlines()]
- fd.close()
-
-badwords = ['sex','masturbate','fuck','rape','dick','pussy','prostitute','hooker',
- 'orgasm','sperm','cunt','penis','shit','piss','urin','bitch','semen','cock',
- 'retard', 'cancer', 'hiv', 'aids']
-tagre = re.compile(r'<.*?>')
-def filter(txt,off):
- _txt = txt.lower()
- if not off:
- for b in badwords:
- if b in _txt:
- return None
- txt = txt.replace(' ','').replace('\n','').replace('\r','')
- txt = txt.replace('','/').replace(' ','/').replace('','*').replace(' ','*')
- txt = txt.replace('"','"').replace('<','<').replace('>','>')
- txt = tagre.sub('',txt)
- return txt
-
-times = {}
-
-def ok(func):
- def newfunc(*args, **kwargs):
- global time
- plugin = args[0]
- channel = args[2].args[0]
- if not channel.startswith('#'):
- delay = 5
- else:
- if not plugin.registryValue('enabled', channel):
- return
- delay = plugin.registryValue('delay', channel)
- if channel not in times.keys():
- times[channel] = time.time()
- elif times[channel] < time.time() - delay:
- times[channel] = time.time()
- else:
- return
- i=0
- func(*args, **kwargs)
- newfunc.__doc__ = func.__doc__
- return newfunc
-
-class Mess(callbacks.PluginRegexp):
- """Random Mess plugin"""
- threaded = True
- regexps = ['hugme']
- hugs = ["hugs %s","gives %s a big hug","gives %s a sloppy wet kiss",
- "huggles %s","squeezes %s","humps %s"]
-
-
- def isCommandMethod(self, name):
- if not callbacks.PluginRegexp.isCommandMethod(self, name):
- if name in mess:
- return True
- else:
- return False
- else:
- return True
-
- def listCommands(self):
- commands = callbacks.PluginRegexp.listCommands(self)
- commands.remove('messcb')
- commands.extend(mess.keys())
- commands.sort()
- return commands
-
- def getCommandMethod(self, command):
- try:
- return callbacks.PluginRegexp.getCommandMethod(self, command)
- except AttributeError:
- return self.messcb
-
- def getCommandHelp(self, command):
- try:
- return callbacks.PluginRegexp.getCommandMethod(self, command)
- except AttributeError:
- return mess[command[0]][0]
-
- def _callCommand(self, command, irc, msg, *args, **kwargs):
- method = self.getCommandMethod(command)
- if command[0] in mess:
- msg.tag('messcmd', command[0])
- try:
- method(irc, msg, *args, **kwargs)
- except Exception, e:
- self.log.exception('Mess: Uncaught exception in %s.', command)
- if conf.supybot.reply.error.detailed():
- irc.error(utils.exnToString(e))
- else:
- irc.replyError()
-
- @ok
- def messcb(self, irc, msg, args, text):
- """General mess"""
- t = threading.Thread(target=self.messthread, args=(irc, msg, args, text))
- t.start()
-
- def messthread(self, irc, msg, args, text):
- global data
- cmd = msg.tagged('messcmd')
- try:
- (doc, loc, tx, off) = mess[cmd]
- except:
- cmd = cmd[1:]
- (doc, loc, tx, off) = mess[cmd]
- if off and not self.registryValue('offensive', msg.args[0]):
- return
- if loc.startswith('http'):
- i = 0
- while i < 5:
- inp = utils.web.getUrl(loc)
- fact = tx.search(inp).group('fact')
- fact = filter(fact,off)
- if fact:
- irc.reply(fact)
- return
- i += 1
- else:
- i = random.randint(0,len(data[loc])-1)
- if '%d' in tx:
- tx = tx % i
- irc.reply(tx + data[loc][i])
- messcb = wrap(messcb, [additional('text')])
-
- # WARNING: depends on an alteration in supybot/callbacks.py - don't do
- # str(s) if s is unicode!
- @ok
- def dice(self, irc, msg, args, count):
- """[]
- Roll the dice, if count is given then roll that many times.
- """
- if not count: count = 1
- elif count > 5: count = 5
- elif count < 1: count = 1
- t = u' '.join([x.__call__([u"\u2680",u"\u2681",u"\u2682",u"\u2683",u"\u2684",u"\u2685"]) for x in [random.choice]*count])
- irc.reply(t)
- dice = wrap(dice, [additional('int')])
-
- @ok
- def hugme(self, irc, msg, match):
- r""".*hug.*ubotu"""
- irc.queueMsg(ircmsgs.action(msg.args[0], self.hugs[random.randint(0,len(self.hugs)-1)] % msg.nick))
-
- @ok
- def fortune(self, irc, msg, args):
- """ Display a fortune cookie """
- f = commands.getoutput('/usr/games/fortune -s')
- f.replace('\t',' ')
- f = f.split('\n')
- for l in f:
- if l:
- irc.reply(l)
- fortune = wrap(fortune)
-
- @ok
- def ofortune(self, irc, msg, args):
- """ Display a possibly offensive fortune cookie """
- if not self.registryValue('offensive', msg.args[0]):
- return
- f = commands.getoutput('/usr/games/fortune -so')
- f.replace('\t',' ')
- f = f.split('\n')
- for l in f:
- if l:
- irc.reply(l)
- ofortune = wrap(ofortune)
-
- @ok
- def futurama(self, irc, msg, args):
- """ Display a futurama quote """
- u = urllib2.urlopen('http://slashdot.org')
- h = [x for x in u.headers.headers if x.startswith('X') and not x.startswith('X-Powered-By')][0]
- irc.reply(h[2:-2].replace(' ',' "',1) + '"')
- futurama = wrap(futurama)
-
- @ok
- def pony(self, irc, msg, args, text):
- """[]
- Can you or have a pony?
- """
- if not text:
- text = 'you'
- irc.reply("No %s can't have a pony, %s!" % (text, msg.nick))
- pony = wrap(pony, [additional('text')])
-
- def callPrecedence(self, irc):
- before = []
- for cb in irc.callbacks:
- if cb.name() == 'IRCLogin':
- before.append(cb)
- return (before, [])
-
- def inFilter(self, irc, msg):
- if not msg.command == 'PRIVMSG':
- return msg
- if not conf.supybot.defaultIgnore():
- return msg
- s = callbacks.addressed(irc.nick, msg)
- if not s:
- return msg
- if checkIgnored(msg.prefix):
- return msg
- try:
- if ircdb.users.getUser(msg.prefix):
- return msg
- except:
- pass
- cmd, args = (s.split(None, 1) + [None])[:2]
- if cmd and cmd[0] in str(conf.supybot.reply.whenAddressedBy.chars.get(msg.args[0])):
- cmd = cmd[1:]
- if cmd in self.listCommands():
- tokens = callbacks.tokenize(s, channel=msg.args[0])
- self.Proxy(irc, msg, tokens)
- return msg
-# self._callCommand([cmd], irc, msg, [])
-Class = Mess
diff --git a/Mess/test.py b/Mess/test.py
deleted file mode 100644
index 4513bc3..0000000
--- a/Mess/test.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2006-2007 Dennis Kaarsemaker
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-from supybot.test import *
-
-class MessTestCase(PluginTestCase):
- plugins = ('Mess',)
diff --git a/Webcal/README.txt b/Webcal/README.txt
deleted file mode 100644
index 754296c..0000000
--- a/Webcal/README.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This plugin can update a topic given an iCal schedule. It's made to work for
-#ubuntu-meeting on Freenode (and iCal from fridge.ubuntu.com) but in theory it
-should work with other iCal feeds too. It's not hard to understand.
-
-Requirements: included ical module and pytz
diff --git a/Webcal/__init__.py b/Webcal/__init__.py
deleted file mode 100644
index 71b44f2..0000000
--- a/Webcal/__init__.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*+ Encoding: utf-8 -*-
-###
-# Copyright (c) 2005-2007 Dennis Kaarsemaker
-# Copyright (c) 2008-2010 Terence Simpson
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-"""
-Update the topic according to an iCal schedule
-"""
-
-import supybot
-import supybot.world as world
-__version__ = "0.3"
-__author__ = supybot.Author('Terence Simpson', 'tsimpson', 'tsimpson@ubuntu.com')
-__contributors__ = {
- supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net"): ['Original Author']
-}
-__url__ = 'https://launchpad.net/ubuntu-bots/'
-
-import config
-reload(config)
-import plugin
-reload(plugin)
-import ical
-reload(ical)
-
-if world.testing:
- import test
-Class = plugin.Class
-configure = config.configure
diff --git a/Webcal/cal.ical b/Webcal/cal.ical
deleted file mode 100644
index a5f52a0..0000000
--- a/Webcal/cal.ical
+++ /dev/null
@@ -1,182 +0,0 @@
-BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:PUBLISH
-X-WR-CALNAME:The Fridge | October 17\, 2008 - December 16\, 2008
-PRODID:-//strange bird labs//Drupal iCal API//EN
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081017T190000Z
-DTEND;VALUE=DATE-TIME:20081017T210000Z
-UID:http://fridge.ubuntu.com/node/1656
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1656
-SUMMARY:Tunisian LoCo Team IRC Meeting
-DESCRIPTION:Location\: #ubuntu-tn
- Agenda\: Team participation to SFD Tunisia 2008.
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081018T130000Z
-DTEND;VALUE=DATE-TIME:20081018T150000Z
-UID:http://fridge.ubuntu.com/node/1571
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1571
-SUMMARY:Xubuntu Community Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/Xubuntu/Meetings
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081021T110000Z
-DTEND;VALUE=DATE-TIME:20081021T130000Z
-UID:http://fridge.ubuntu.com/node/1558
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1558
-SUMMARY:Community Council Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081021T110000Z
-DTEND;VALUE=DATE-TIME:20081021T120000Z
-UID:http://fridge.ubuntu.com/node/1678
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1678
-SUMMARY:Asia Oceania Membership Board Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081021T140000Z
-DTEND;VALUE=DATE-TIME:20081021T160000Z
-UID:http://fridge.ubuntu.com/node/1662
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1662
-SUMMARY:Technical Board Meeting
-DESCRIPTION:
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081021T150000Z
-DTEND;VALUE=DATE-TIME:20081021T160000Z
-UID:http://fridge.ubuntu.com/node/1681
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1681
-SUMMARY:Server Team Meeting
-DESCRIPTION:Location\: #ubuntu-meeting on IRC
- Agenda\: https\://wiki.ubuntu.com/ServerTeam/Meeting
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081021T170000Z
-DTEND;VALUE=DATE-TIME:20081021T180000Z
-UID:http://fridge.ubuntu.com/node/1683
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1683
-SUMMARY:Kernel Team Meeting
-DESCRIPTION:Location\: #ubuntu-meeting in IRC
- Agenda\: Not listed as of publication
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081022T230000Z
-DTEND;VALUE=DATE-TIME:20081023T000000Z
-UID:http://fridge.ubuntu.com/node/1667
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1667
-SUMMARY:Forum Council Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081028T160000Z
-DTEND;VALUE=DATE-TIME:20081028T170000Z
-UID:http://fridge.ubuntu.com/node/1682
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1682
-SUMMARY:Server Team Meeting
-DESCRIPTION:Location\: #ubuntu-meeting on IRC
- Agenda\: https\://wiki.ubuntu.com/ServerTeam/Meeting
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081028T170000Z
-DTEND;VALUE=DATE-TIME:20081028T180000Z
-UID:http://fridge.ubuntu.com/node/1684
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1684
-SUMMARY:Kernel Team Meeting
-DESCRIPTION:Location\: #ubuntu-meeting in IRC
- Agenda\: Not listed as of publication
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081104T140000Z
-DTEND;VALUE=DATE-TIME:20081104T140000Z
-UID:http://fridge.ubuntu.com/node/1663
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1663
-SUMMARY:Technical Board Meeting
-DESCRIPTION:
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081104T210000Z
-DTEND;VALUE=DATE-TIME:20081104T230000Z
-UID:http://fridge.ubuntu.com/node/1553
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1553
-SUMMARY:Community Council Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081106T000000Z
-DTEND;VALUE=DATE-TIME:20081106T010000Z
-UID:http://fridge.ubuntu.com/node/1547
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1547
-SUMMARY:Maryland LoCo IRC Meeting
-DESCRIPTION:Location\: #ubuntu-us-md
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081118T110000Z
-DTEND;VALUE=DATE-TIME:20081118T130000Z
-UID:http://fridge.ubuntu.com/node/1559
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1559
-SUMMARY:Community Council Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081202T210000Z
-DTEND;VALUE=DATE-TIME:20081202T230000Z
-UID:http://fridge.ubuntu.com/node/1554
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1554
-SUMMARY:Community Council Meeting
-DESCRIPTION:Location\: #ubuntu-meeting
- Agenda\: https\://wiki.ubuntu.com/CommunityCouncilAgenda
-
-END:VEVENT
-BEGIN:VEVENT
-DTSTAMP;VALUE=DATE:20081017T202549Z
-DTSTART;VALUE=DATE-TIME:20081204T000000Z
-DTEND;VALUE=DATE-TIME:20081204T010000Z
-UID:http://fridge.ubuntu.com/node/1548
-URL;VALUE=URI:http://fridge.ubuntu.com/node/1548
-SUMMARY:Maryland LoCo IRC Meeting
-DESCRIPTION:Location\: #ubuntu-us-md
-
-END:VEVENT
-END:VCALENDAR
diff --git a/Webcal/config.py b/Webcal/config.py
deleted file mode 100644
index 556097a..0000000
--- a/Webcal/config.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2005-2007 Dennis Kaarsemaker
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-import supybot.conf as conf
-import supybot.registry as registry
-
-def configure(advanced):
- from supybot.questions import expect, something, yn, output
-
- def anything(prompt, default=None):
- """Because supybot is pure fail"""
- from supybot.questions import expect
- return expect(prompt, [], default=default)
-
- Webcal = conf.registerPlugin('Webcal', True)
-
- output("Every option, except for the default channel and URL to a list of time zones, is channel-specific.")
- output("The values you enter here will be the defaults unless overridden by a channel-specific value")
- doTopic = yn("Manage the topic for all channels?", default=Webcal.doTopic._default)
- url = anything("What is the default URL to the iCal feed, for all channels?", default=Webcal.url._default)
- defaultChannel = anything("What channel should be default when none is given?", default=Webcal.defaultChannel._default)
- tzUrl = anything("What is the URL to the list of available time zonez?", default=Webcal.tzUrl._default)
-
- if advanced:
- filter = anything("What should the filter be for the iCal feed, for all channels?", default=Webcal.filter._default)
- topic = anything("What template should be used for the topic, for all channels", default=Webcal.topic._default)
- else:
- filter = Webcal.filter._default
- topic = Webcal.topic._default
-
- Webcal.doTopic.setValue(doTopic)
- Webcal.url.setValue(url)
- Webcal.defaultChannel.setValue(defaultChannel)
- Webcal.tzUrl.setValue(tzUrl)
- Webcal.filter.setValue(filter)
- Webcal.topic.setValue(topic)
-
-Webcal = conf.registerPlugin('Webcal')
-conf.registerChannelValue(conf.supybot.plugins.Webcal, 'url',
- registry.String('',"""Webcal URL for the channel"""))
-conf.registerChannelValue(conf.supybot.plugins.Webcal, 'filter',
- registry.String('',"""What to filter on in the ical feed"""))
-conf.registerChannelValue(conf.supybot.plugins.Webcal, 'topic',
- registry.String('',"""Topic template"""))
-conf.registerChannelValue(conf.supybot.plugins.Webcal, 'doTopic',
- registry.Boolean(False,"""Whether to manage the topic"""))
-conf.registerGlobalValue(conf.supybot.plugins.Webcal, 'defaultChannel',
- registry.String('',"""Default channel to determine schedule for /msg replies"""))
-conf.registerGlobalValue(conf.supybot.plugins.Webcal, 'tzUrl',
- registry.String('http://ubottu.com/timezones.html', """URL to the list of timezones supported by the Webcal plugin"""))
diff --git a/Webcal/ical.py b/Webcal/ical.py
deleted file mode 100644
index 5f47e1d..0000000
--- a/Webcal/ical.py
+++ /dev/null
@@ -1,296 +0,0 @@
-#!/usr/bin/env python
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2008-2010 Terence Simpson
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-import sys, os
-sys.path.append(os.path.dirname(__file__))
-from icalendar import Calendar, cal, prop
-from dateutil import tz as tzmod
-from cStringIO import StringIO
-import pytz
-import urllib2
-import datetime
-import rruler
-
-DEB_OBJ = None
-
-SECONDS_PER_DAY=24*60*60
-def seconds(timediff):
- return SECONDS_PER_DAY * timediff.days + timediff.seconds
-
-def toTz(date, tz):
- assert isinstance(tz, datetime.tzinfo), "tz must be a tzinfo type"
- if isinstance(date, datetime.datetime):
- try:
- return date.astimezone(tz)
- except:
- return datetime.datetime.combine(date.date(), datetime.time(date.time().hour, date.time().minute, date.time().second, tzinfo=tz))
- elif isinstance(datetime.date):
- return datetime.datetime.combine(date, datetime.time(0, 0, 0, tzinfo=tz))
-
-class ICalReader:
- def __init__(self, data):
- self.events = []
- self.timezones = {}
- self.raw_data = data
- self.readEvents()
-
- def readEvents(self):
- self.events = []
- self.timezones = {}
- parser = Calendar.from_string(self.raw_data)
- tzs = parser.walk("vtimezone")
- self.parseTzs(tzs)
- events = parser.walk("vevent")
- for event in events:
- res = self.parseEvent(event)
- if res:
- self.events.append(res)
-
- def parseTzs(self, tzs):
- if not tzs:
- return
- for tz in tzs:
- if 'X-LIC-LOCATION' in tz:
- del tz['X-LIC-LOCATION']
- data = ''.join([str(i) for i in tzs])
- data = '\r\n'.join([i for i in data.splitlines() if i.strip()])
- fd = StringIO(data)
- times = tzmod.tzical(fd)
- for tz in times.keys():
- self.timezones[tz] = times.get(tz)
-
- def parseEvent(self, e):
- for k in ["dtstart", "dtend", "summary"]:
- if not k in e:
- return
- if not isinstance(e['dtstart'].dt, datetime.datetime):
- return
- return ICalEvent.from_event(e, self)
- startDate = endDate = rule = summary = None
- startDate = self.parseDate(e.get("dtstart"))
- endDate = self.parseDate(e.get("dtend"))
- rule = e.get("RRULE")
- summary = e.get("summary")
- if e.get("exdate"):
- event.addExceptionDate(e['EXDATE'].ical()[7:])
- if not startDate or not endDate or not summary: # Bad event
- return
-
- event = ICalEvent()
- event.raw_data = str(e)
- event.summary = summary
- event.startDate = startDate
- event.endDate = endDate
- if rule:
- event.addRecurrenceRule(rule)
- return event
-
- def parseDate(self, date):
- if not date:
- return
- tz = pytz.UTC
- if 'tzid' in date.params:
- tz = self.timezones[date.params['tzid']]
- for attr in ['hour', 'minute', 'second']:
- if not hasattr(date.dt, attr):
- return
- return toTz(date.dt, tz)
-# return datetime.datetime(date.dt.year, date.dt.month, date.dt.day, date.dt.hour, date.dt.minute, date.dt.second, tzinfo=tz)
-
- def selectEvents(self, selectFunction):
- self.events.sort()
- events = filter(selectFunction, self.events)
- return events
-
- def todaysEvents(self, event):
- return event.startsToday()
-
- def tomorrowsEvents(self, event):
- return event.startsTomorrow()
-
- def eventsFor(self, date):
- self.events.sort()
- ret = []
- for event in self.events:
- if event.startsOn(date):
- ret.append(event)
- return re
-
-
-#class ICalEvent:
-# def __init__(self):
-# self.exceptionDates = []
-# self.dateSet = None
-#
-# def __str__(self):
-# return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
-
-class ICalEvent(cal.Event):
- def __init__(self, *args, **kwargs):
- self.exceptionDates = []
- self.dateSet = None
- self.__parent = super(ICalEvent, self)
- self.__parent.__init__(self, *args, **kwargs)
-
- @classmethod
- def from_event(cls, event, parent):
- global DEB_OBJ
- x = cls(**dict(event))
- x.__dict__ = event.__dict__
- x.exceptionDates = []
- x.dateSet = None
- x.summary = x['summary']
- x.timezone = x['dtstart'].dt.tzinfo
- x.startDate = parent.parseDate(x['dtstart'])
- x.endDate = parent.parseDate(x['dtend'])
- if not x.timezone:
- x.timezone = pytz.UTC
- x.startDate = parent.parseDate(x['dtstart'])
- x.endDate = parent.parseDate(x['dtend'])
- x.raw_data = str(x)
- if 'rrule' in event:
- x.addRecurrenceRule(event['rrule'])
- if x.summary == "Server Team Meeting":
- DEB_OBJ = x
- return x
-
- def __str__(self):
- return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
-
- def __eq__(self, otherEvent):
- return self.startTime() == otherEvent.startTime()
-
- def __lt__(self, otherEvent):
- return self.startTime() < otherEvent.startTime()
-
- def __gt__(self, otherEvent):
- return self.startTime() > otherEvent.startTime()
-
- def __ge__(self, otherEvent):
- return self.startTime() >= otherEvent.startTime()
-
- def __le__(self, otherEvent):
- return self.startTime() <= otherEvent.startTime()
-
- def addExceptionDate(self, date):
- self.exceptionDates.append(date)
-
- def addRecurrenceRule(self, rule):
- self.dateSet = DateSet(self.startDate, self.endDate, rule)
-
- def startsToday(self):
- return self.startsOn(datetime.datetime.today())
-
- def startsTomorrow(self):
- tomorrow = datetime.datetime.today() + datetime.timedelta(1)
-# tomorrow = datetime.datetime.fromtimestamp(time.time() + SECONDS_PER_DAY)
- return self.startsOn(tomorrow)
-
- def startsOn(self, date):
- return (self.startDate.year == date.year and
- self.startDate.month == date.month and
- self.startDate.day == date.day or
- (self.dateSet and self.dateSet.includes(date)))
-
- def startTime(self):
- now = datetime.datetime.now(pytz.UTC)
- if self.dateSet and self.startDate < now:
- dates = self.dateSet.getRecurrence()
- for date in dates:
- if date.date() >= now.date():
- if date.date() > now.date() or (date.date() == now.date and date.astimezone(pytz.UTC).time() >= now.time()):
- return toTz(datetime.datetime.combine(date,self.startDate.time()), self.startDate.tzinfo)
- return self.startDate
-
- def endTime(self):
- now = datetime.datetime.now(pytz.UTC).date()
- if self.dateSet and self.endDate.date() < now:
- return toTz(datetime.datetime.combine(self.startTime().date(), self.endDate.time()), self.startDate.tzinfo)
- return self.endDate
-
- def schedule(self, timezone=None):
- if not timezone:
- return "%s UTC: %s" % (self.startTime().astimezone(pytz.UTC).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
- if isinstance(timezone, basestring):
- return "%s: %s" % (self.startTime().astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
- return "%s: %s" % (self.startTime().astimezone(timezone).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
-
- def is_on(self):
- now = datetime.datetime.now(pytz.UTC)
- return self.startTime() >= now and self.endTime() < now
-
- def has_passed(self):
- if self.dateSet:
- return toTz(datetime.datetime.combine(self.startTime().date(), self.endDate.time()), self.startDate.tzinfo) < datetime.datetime.now(pytz.UTC)
- return self.endDate < datetime.datetime.now(pytz.UTC)
-
- def seconds_to_go(self):
- return seconds(self.startTime() - datetime.datetime.now(pytz.UTC))
-
- def seconds_ago(self):
- return seconds(datetime.datetime.now(pytz.UTC) - self.endTime())
-
- def time_to_go(self):
- if self.endTime() < datetime.datetime.now(pytz.UTC):
- return False
- delta = self.startTime() - datetime.datetime.now(pytz.UTC)
- s = ''
- if delta.days:
- if delta.days != 1:
- s = 's'
- return '%d day%s' % (delta.days, s)
- h = ''
- if delta.seconds > 7200:
- s = 's'
- if delta.seconds > 3600:
- h = '%d hour%s ' % (int(delta.seconds/3600),s)
- s = ''
- minutes = (delta.seconds % 3600) / 60
- if minutes != 1:
- s = 's'
- return '%s%d minute%s' % (h,minutes,s)
-
-class DateSet:
- def __init__(self, startDate, endDate, rule):
- self.startDate = startDate
- self.endDate = endDate
- self.frequency = None
- self.count = None
- self.untilDate = None
- self.byMonth = None
- self.byDate = None
- self.dates = None
- self.parseRecurrenceRule(rule)
-
- def parseRecurrenceRule(self, rule):
- freq = rruler.rrule_map[rule.pop('freq')[0]]
- now = datetime.datetime.now(self.startDate.tzinfo)
- rule['dtstart'] = now
- rule['until'] = now + datetime.timedelta(60)
- self.recurrence = rruler.rrule_wrapper(freq, **rule)
-
- def getRecurrence(self):
- if not self.dates:
- self.dates = []
- for x in list(self.recurrence):
- self.dates.append(toTz(x, self.startDate.tzinfo))
- self.dates.append(self.startDate)
- return self.dates
-
- def includes(self, date):
- if isinstance(date, datetime.datetime):
- date = date.date()
- return date in [x.date() for x in self.getRecurrence()]
diff --git a/Webcal/ical.py.bak b/Webcal/ical.py.bak
deleted file mode 100644
index 1d932ce..0000000
--- a/Webcal/ical.py.bak
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/usr/bin/python
-import sys, os
-sys.path.append(os.path.dirname(__file__))
-import icalendar
-reload(icalendar)
-from icalendar import Calendar, cal, prop
-from dateutil import tz as tzmod
-from cStringIO import StringIO
-import pytz
-import urllib2
-import datetime, time
-import rruler
-reload(rruler)
-
-SECONDS_PER_DAY=24*60*60
-def seconds(timediff):
- return SECONDS_PER_DAY * timediff.days + timediff.seconds
-
-class ICalReader:
- def __init__(self, data):
- self.events = []
- self.timezones = {}
- self.raw_data = data
- self.readEvents()
-
- def readEvents(self):
- self.events = []
- self.timezones = {}
- parser = Calendar.from_string(self.raw_data)
- tzs = parser.walk("vtimezone")
- self.parseTzs(tzs)
- events = parser.walk("vevent")
- for event in events:
- res = self.parseEvent(event)
- if res:
- self.events.append(res)
-
- def parseTzs(self, tzs):
- if not tzs:
- return
- for tz in tzs:
- if 'X-LIC-LOCATION' in tz:
- del tz['X-LIC-LOCATION']
- data = ''.join([str(i) for i in tzs])
- data = '\r\n'.join([i for i in data.splitlines() if i.strip()])
- fd = StringIO(data)
- times = tzmod.tzical(fd)
- for tz in times.keys():
- self.timezones[tz] = times.get(tz)
-
- def parseEvent(self, e):
- for k in ["dtstart", "dtend", "summary"]:
- if not k in e:
- return
- if not isinstance(e['dtstart'].dt, datetime.datetime):
- return
- return ICalEvent.from_event(e, self)
- startDate = endDate = rule = summary = None
- startDate = self.parseDate(e.get("dtstart"))
- endDate = self.parseDate(e.get("dtend"))
- rule = e.get("RRULE")
- summary = e.get("summary")
- if e.get("exdate"):
- event.addExceptionDate(e['EXDATE'].ical()[7:])
- if not startDate or not endDate or not summary: # Bad event
- return
-
- event = ICalEvent()
- event.raw_data = str(e)
- event.summary = summary
- event.startDate = startDate
- event.endDate = endDate
- if rule:
- event.addRecurrenceRule(rule)
- return event
-
- @staticmethod
- def toTz(date, tz):
- return datetime.datetime(date.year, date.month, date.day, date.hour, date.minute, date.second, tzinfo=tz)
-
- def parseDate(self, date):
- if not date:
- return
- tz = pytz.UTC
- if 'tzid' in date.params:
- tz = self.timezones[date.params['tzid']]
- for attr in ['hour', 'minute', 'second']:
- if not hasattr(date.dt, attr):
- return
- return self.toTz(date.dt, tz)
-# return datetime.datetime(date.dt.year, date.dt.month, date.dt.day, date.dt.hour, date.dt.minute, date.dt.second, tzinfo=tz)
-
- def selectEvents(self, selectFunction):
- self.events.sort()
- events = filter(selectFunction, self.events)
- return events
-
- def todaysEvents(self, event):
- return event.startsToday()
-
- def tomorrowsEvents(self, event):
- return event.startsTomorrow()
-
- def eventsFor(self, date):
- self.events.sort()
- ret = []
- for event in self.events:
- if event.startsOn(date):
- ret.append(event)
- return re
-
-
-#class ICalEvent:
-# def __init__(self):
-# self.exceptionDates = []
-# self.dateSet = None
-#
-# def __str__(self):
-# return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
-
-class ICalEvent(cal.Event):
- def __init__(self, *args, **kwargs):
- self.exceptionDates = []
- self.dateSet = None
- self.__parent = super(ICalEvent, self)
- self.__parent.__init__(self, *args, **kwargs)
-
- @classmethod
- def from_event(cls, event, parent):
- x = cls(**dict(event))
- x.__dict__ = event.__dict__
- x.summary = x['summary']
- x.timezone = x['dtstart'].dt.tzinfo
- x.startDate = parent.parseDate(x['dtstart'])
- x.endDate = parent.parseDate(x['dtend'])
- if not x.timezone:
- x.timezone = pytz.UTC
- x.startDate = parent.parseDate(x['dtstart'])
- x.endDate = parent.parseDate(x['dtend'])
- x.raw_data = str(x)
- if 'rrule' in event:
- x.addRecurrenceRule(event['rrule'])
- return x
-
- def __str__(self):
- return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
-
- def __eq__(self, otherEvent):
- return self.startDate == otherEvent.startDate
-
- def __lt__(self, otherEvent):
- return self.startDate < otherEvent.startDate
-
- def __gt__(self, otherEvent):
- return self.startDate > otherEvent.startDate
-
- def __ge__(self, otherEvent):
- return self.startDate >= otherEvent.startDate
-
- def __le__(self, otherEvent):
- return self.startDate <= otherEvent.startDate
-
- def addExceptionDate(self, date):
- self.exceptionDates.append(date)
-
- def addRecurrenceRule(self, rule):
- self.dateSet = DateSet(self.startDate, self.endDate, rule)
-
- def startsToday(self):
- return self.startsOn(datetime.datetime.today())
-
- def startsTomorrow(self):
- tomorrow = datetime.datetime.fromtimestamp(time.time() + SECONDS_PER_DAY)
- return self.startsOn(tomorrow)
-
- def startsOn(self, date):
- return (self.startDate.year == date.year and
- self.startDate.month == date.month and
- self.startDate.day == date.day or
- (self.dateSet and self.dateSet.includes(date)))
-
- def startTime(self):
- return self.startDate
-
- def schedule(self, timezone=None):
- if not timezone:
- return "%s UTC: %s" % (self.startDate.strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
- return "%s: %s" % (self.startDate.astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M %Z"), self.summary.replace('Meeting','').strip())
-
- def is_on(self):
- return self.startDate < datetime.datetime.now(pytz.UTC) and self.endDate > datetime.datetime.now(pytz.UTC)
-
- def has_passed(self):
- return self.endDate < datetime.datetime.now(pytz.UTC)
-
- def seconds_to_go(self):
- return seconds(self.startDate - datetime.datetime.now(pytz.UTC))
-
- def seconds_ago(self):
- return seconds(datetime.datetime.now(pytz.UTC) - self.endDate)
-
- def time_to_go(self):
- if self.endDate < datetime.datetime.now(pytz.UTC):
- return False
- delta = self.startDate - datetime.datetime.now(pytz.UTC)
- s = ''
- if delta.days:
- if delta.days != 1:
- s = 's'
- return '%d day%s' % (delta.days, s)
- h = ''
- if delta.seconds > 7200:
- s = 's'
- if delta.seconds > 3600:
- h = '%d hour%s ' % (int(delta.seconds/3600),s)
- s = ''
- minutes = (delta.seconds % 3600) / 60
- if minutes != 1:
- s = 's'
- return '%s%d minute%s' % (h,minutes,s)
-
-class DateSet:
- def __init__(self, startDate, endDate, rule):
- self.startDate = startDate
- self.endDate = endDate
- self.frequency = None
- self.count = None
- self.untilDate = None
- self.byMonth = None
- self.byDate = None
- self.parseRecurrenceRule(rule)
-
- def parseRecurrenceRule(self, rule):
- freq = rruler.rrule_map[rule.pop('freq')[0]]
- self.recurrence = rruler.rrule_wrapper(freq, **rule)
-# if 'freq' in rule:
-# self.frequency = rule['freq']
-# if 'count' in rule:
-# self.count = rule['count']
-# if 'until' in rule:
-## self.untilDate = rule['until'][0].strftime("%Y%m%dT%H%M%SZ")
-# self.untilDate = rule['until'][0]
-# if 'interval' in rule:
-# self.interval = rule['interval']
-# if 'bymonth' in rule:
-# self.myMonth = rule['bymonth']
-# if 'byday' in rule:
-# self.byDay = rule['byday']
-
- def includes(self, date):
- if isinstance(date, datetime.datetime):
- date = date.date()
- return date in [x.date() for x in list(self.recurrence)] or date == self.startDate.date()
-# if date == self.startDate:
-# return True
-#
-# if self.untilDate and date > self.untilDate:
-# return False
-#
-# if self.frequency == 'DAILY':
-# increment = 1
-# if self.interval:
-# increment = self.interval
-# d = self.startDate
-# counter = 0
-# while(d < date):
-# if self.count:
-# counter += 1
-# if counter >= self.count:
-# return False
-#
-# d = d.replace(day=d.day+1)
-#
-# if (d.day == date.day and
-# d.year == date.year and
-# d.month == date.month):
-# return True
-#
-# elif self.frequency == 'WEEKLY':
-# if self.startDate.weekday() == date.weekday():
-# return True
-# else:
-# if self.endDate:
-# for n in range(0, self.endDate.day - self.startDate.day):
-# newDate = self.startDate.replace(day=self.startDate.day+n)
-# if newDate.weekday() == date.weekday():
-# return True
-#
-# elif self.frequency == 'MONTHLY':
-# if self.startDate.month == date.month:
-# if self.startDate.weekday() == date.weekday():
-# return True
-#
-# elif self.frequency == 'YEARLY':
-# if (self.startDate.month == date.month) and (self.startDate.day == date.day):
-# return True
-#
-# return False
-
diff --git a/Webcal/ical.py.bak.bac2 b/Webcal/ical.py.bak.bac2
deleted file mode 100644
index 0152082..0000000
--- a/Webcal/ical.py.bak.bac2
+++ /dev/null
@@ -1,286 +0,0 @@
-#!/usr/bin/python
-# Slightly modified version of the iCal module found at
-# http://www.devoesquared.com/Software/iCal_Module
-
-import os
-import os.path
-import re
-import datetime
-import time
-import pytz # pytz can be found on http://pytz.sourceforge.net
-
-parent = None
-
-def log(x):
- if not parent:
- return
- parent.log.info(x)
-
-SECONDS_PER_DAY=24*60*60
-def seconds(timediff):
- return SECONDS_PER_DAY * timediff.days + timediff.seconds
-
-class ICalReader:
-
- def __init__(self, data):
- self.events = []
- self.raw_data = data.replace('\r','')
- self.readEvents()
-
- def readEvents(self):
- self.events = []
- lines = self.raw_data.split('\n')
- inEvent = False
- eventLines = []
- stRegex = re.compile("^BEGIN:VEVENT")
- enRegex = re.compile("^END:VEVENT")
- for line in lines:
- if stRegex.match(line):
- inEvent = True
- eventLines = []
- if inEvent:
- eventLines.append(line)
- if enRegex.match(line):
- inEvent = False
- event = self.parseEvent(eventLines)
- if event:
- self.events.append(event)
-
- self.events.sort()
- return self.events
-
- def parseEvent(self, lines):
- event = ICalEvent()
- event.raw_data = "\n".join(lines)
- startDate = None
- rule = None
- endDate = None
- reSummary = re.compile("^SUMMARY:(.*)")
- reDstart = re.compile("^DTSTART(.*):([0-9]+T[0-9]+)")
- reDend = re.compile("^DTEND(.*):([0-9]+T[0-9]+)")
- reExdata = re.compile("^EXDATE:([0-9]+T[0-9]+)")
- reRrule = re.compile("^RRULE:(.*)")
- for line in lines:
- match = False
- if reSummary.match(line):
- event.summary = reSummary.match(line).group(1)
- elif reDstart.match(line):
- startDate = self.parseDate(*reDstart.match(line).groups())
- elif reDend.match(line):
- endDate = self.parseDate(*reDend.match(line).groups())
- elif reExdata.match(line):
- event.addExceptionDate(reExdate.match(line).group(1))
- elif reRrule.match(line):
- rule = reRrule.match(line).group(1)
-
- event.startDate = startDate
- event.endDate = endDate
-
- if rule:
- event.addRecurrenceRule(rule)
-
- if not startDate or not endDate:
- return None
- return event
-
- def parseDate(self, tz, dateStr):
- year = int(dateStr[0:4])
- if year < 1970:
- year = 1970
-
- month = int(dateStr[4:4+2])
- day = int(dateStr[6:6+2])
- try:
- hour = int(dateStr[9:9+2])
- minute = int(dateStr[11:11+2])
- except:
- hour = 0
- minute = 0
- if tz:
- return datetime.datetime(year, month, day, hour, minute, tzinfo=pytz.timezone(tz[6:]))
- return datetime.datetime(year, month, day, hour, minute, tzinfo=pytz.UTC)
-
- def selectEvents(self, selectFunction):
- note = datetime.datetime.today()
- self.events.sort()
- events = filter(selectFunction, self.events)
- return events
-
- def todaysEvents(self, event):
- return event.startsToday()
-
- def tomorrowsEvents(self, event):
- return event.startsTomorrow()
-
- def eventsFor(self, date):
- note = datetime.datetime.today()
- self.events.sort()
- ret = []
- for event in self.events:
- if event.startsOn(date):
- ret.append(event)
- return ret
-
-
-class ICalEvent:
- def __init__(self):
- self.exceptionDates = []
- self.dateSet = None
-
- def __str__(self):
- return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate)
-
- def __eq__(self, otherEvent):
- return self.startDate == otherEvent.startDate
-
- def __lt__(self, otherEvent):
- return self.startDate < otherEvent.startDate
-
- def __gt__(self, otherEvent):
- return self.startDate > otherEvent.startDate
-
- def __ge__(self, otherEvent):
- return self.startDate >= otherEvent.startDate
-
- def __le__(self, otherEvent):
- return self.startDate <= otherEvent.startDate
-
- def addExceptionDate(self, date):
- self.exceptionDates.append(date)
-
- def addRecurrenceRule(self, rule):
- self.dateSet = DateSet(self.startDate, self.endDate, rule)
-
- def startsToday(self):
- return self.startsOn(datetime.datetime.today())
-
- def startsTomorrow(self):
- tomorrow = datetime.datetime.fromtimestamp(time.time() + SECONDS_PER_DAY)
- return self.startsOn(tomorrow)
-
- def startsOn(self, date):
- return (self.startDate.year == date.year and
- self.startDate.month == date.month and
- self.startDate.day == date.day or
- (self.dateSet and self.dateSet.includes(date)))
-
- def startTime(self):
- return self.startDate
-
- def schedule(self, timezone=None):
- if not timezone:
- return "%s UTC: %s" % (self.startDate.strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
- return "%s: %s" % (self.startDate.astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip())
-
- def is_on(self):
- return self.startDate < datetime.datetime.now(pytz.UTC) and self.endDate > datetime.datetime.now(pytz.UTC)
-
- def has_passed(self):
- return self.endDate < datetime.datetime.now(pytz.UTC)
-
- def seconds_to_go(self):
- return seconds(self.startDate - datetime.datetime.now(pytz.UTC))
-
- def seconds_ago(self):
- return seconds(datetime.datetime.now(pytz.UTC) - self.endDate)
-
- def time_to_go(self):
- if self.endDate < datetime.datetime.now(pytz.UTC):
- return False
- delta = self.startDate - datetime.datetime.now(pytz.UTC)
- s = ''
- if delta.days:
- if delta.days != 1:
- s = 's'
- return '%d day%s' % (delta.days, s)
- h = ''
- if delta.seconds > 7200:
- s = 's'
- if delta.seconds > 3600:
- h = '%d hour%s ' % (int(delta.seconds/3600),s)
- s = ''
- minutes = (delta.seconds % 3600) / 60
- if minutes != 1:
- s = 's'
- return '%s%d minute%s' % (h,minutes,s)
-
-
-class DateSet:
- def __init__(self, startDate, endDate, rule):
- self.startDate = startDate
- self.endDate = endDate
- self.frequency = None
- self.count = None
- self.untilDate = None
- self.byMonth = None
- self.byDate = None
- self.parseRecurrenceRule(rule)
-
- def parseRecurrenceRule(self, rule):
- if re.compile("FREQ=(.*?);").match(rule) :
- self.frequency = re.compile("FREQ=(.*?);").match(rule).group(1)
-
- if re.compile("COUNT=(\d*)").match(rule) :
- self.count = int(re.compile("COUNT=(\d*)").match(rule).group(1))
-
- if re.compile("UNTIL=(.*?);").match(rule) :
-# self.untilDate = DateParser.parse(re.compile("UNTIL=(.*?);").match(rule).group(1))
- self.untilDate = re.compile("UNTIL=(.*?);").match(rule).group(1)
-
- if re.compile("INTERVAL=(\d*)").match(rule) :
- self.interval = int(re.compile("INTERVAL=(\d*)").match(rule).group(1))
-
- if re.compile("BYMONTH=(.*?);").match(rule) :
- self.byMonth = re.compile("BYMONTH=(.*?);").match(rule).group(1)
-
- if re.compile("BYDAY=(.*?);").match(rule) :
- self.byDay = re.compile("BYDAY=(.*?);").match(rule).group(1)
-
-
- def includes(self, date):
- if date == self.startDate:
- return True
-
- if self.untilDate and date > self.untilDate:
- return False
-
- if self.frequency == 'DAILY':
- increment = 1
- if self.interval:
- increment = self.interval
- d = self.startDate
- counter = 0
- while(d < date):
- if self.count:
- counter += 1
- if counter >= self.count:
- return False
-
- d = d.replace(day=d.day+1)
-
- if (d.day == date.day and
- d.year == date.year and
- d.month == date.month):
- return True
-
- elif self.frequency == 'WEEKLY':
- if self.startDate.weekday() == date.weekday():
- return True
- else:
- if self.endDate:
- for n in range(0, self.endDate.day - self.startDate.day):
- newDate = self.startDate.replace(day=self.startDate.day+n)
- if newDate.weekday() == date.weekday():
- return True
-
- elif self.frequency == 'MONTHLY':
- if self.startDate.month == date.month:
- if self.startDate.weekday() == date.weekday():
- return True
-
- elif self.frequency == 'YEARLY':
- if (self.startDate.month == date.month) and (self.startDate.day == date.day):
- return True
-
- return False
-
diff --git a/Webcal/icalendar/__init__.py b/Webcal/icalendar/__init__.py
deleted file mode 100644
index e2ce67f..0000000
--- a/Webcal/icalendar/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- Encoding: utf-8 -*-
-# Components
-from icalendar.cal import Calendar, Event, Todo, Journal
-from icalendar.cal import FreeBusy, Timezone, Alarm, ComponentFactory
-
-# Property Data Value Types
-from icalendar.prop import vBinary, vBoolean, vCalAddress, vDatetime, vDate, \
- vDDDTypes, vDuration, vFloat, vInt, vPeriod, \
- vWeekday, vFrequency, vRecur, vText, vTime, vUri, \
- vGeo, vUTCOffset, TypesFactory
-
-# useful tzinfo subclasses
-from icalendar.prop import FixedOffset, UTC, LocalTimezone
-
-# Parameters and helper methods for splitting and joining string with escaped
-# chars.
-from icalendar.parser import Parameters, q_split, q_join
diff --git a/Webcal/icalendar/cal.py b/Webcal/icalendar/cal.py
deleted file mode 100644
index c1603bd..0000000
--- a/Webcal/icalendar/cal.py
+++ /dev/null
@@ -1,534 +0,0 @@
-# -*- coding: latin-1 -*-
-
-"""
-
-Calendar is a dictionary like Python object that can render itself as VCAL
-files according to rfc2445.
-
-These are the defined components.
-
-"""
-
-# from python
-from types import ListType, TupleType
-SequenceTypes = (ListType, TupleType)
-import re
-
-# from this package
-from icalendar.caselessdict import CaselessDict
-from icalendar.parser import Contentlines, Contentline, Parameters
-from icalendar.parser import q_split, q_join
-from icalendar.prop import TypesFactory, vText
-
-
-######################################
-# The component factory
-
-class ComponentFactory(CaselessDict):
- """
- All components defined in rfc 2445 are registered in this factory class. To
- get a component you can use it like this.
-
- >>> factory = ComponentFactory()
- >>> component = factory['VEVENT']
- >>> event = component(dtstart='19700101')
- >>> event.as_string()
- 'BEGIN:VEVENT\\r\\nDTSTART:19700101\\r\\nEND:VEVENT\\r\\n'
-
- >>> factory.get('VCALENDAR', Component)
-
- """
-
- def __init__(self, *args, **kwargs):
- "Set keys to upper for initial dict"
- CaselessDict.__init__(self, *args, **kwargs)
- self['VEVENT'] = Event
- self['VTODO'] = Todo
- self['VJOURNAL'] = Journal
- self['VFREEBUSY'] = FreeBusy
- self['VTIMEZONE'] = Timezone
- self['VALARM'] = Alarm
- self['VCALENDAR'] = Calendar
-
-
-# These Properties have multiple property values inlined in one propertyline
-# seperated by comma. Use CaselessDict as simple caseless set.
-INLINE = CaselessDict(
- [(cat, 1) for cat in ('CATEGORIES', 'RESOURCES', 'FREEBUSY')]
-)
-
-_marker = []
-
-class Component(CaselessDict):
- """
- Component is the base object for calendar, Event and the other components
- defined in RFC 2445. normally you will not use this class directy, but
- rather one of the subclasses.
-
- A component is like a dictionary with extra methods and attributes.
- >>> c = Component()
- >>> c.name = 'VCALENDAR'
-
- Every key defines a property. A property can consist of either a single
- item. This can be set with a single value
- >>> c['prodid'] = '-//max m//icalendar.mxm.dk/'
- >>> c
- VCALENDAR({'PRODID': '-//max m//icalendar.mxm.dk/'})
-
- or with a list
- >>> c['ATTENDEE'] = ['Max M', 'Rasmussen']
-
- if you use the add method you don't have to considder if a value is a list
- or not.
- >>> c = Component()
- >>> c.name = 'VEVENT'
- >>> c.add('attendee', 'maxm@mxm.dk')
- >>> c.add('attendee', 'test@example.dk')
- >>> c
- VEVENT({'ATTENDEE': [vCalAddress('maxm@mxm.dk'), vCalAddress('test@example.dk')]})
-
- You can get the values back directly
- >>> c.add('prodid', '-//my product//')
- >>> c['prodid']
- vText(u'-//my product//')
-
- or decoded to a python type
- >>> c.decoded('prodid')
- u'-//my product//'
-
- With default values for non existing properties
- >>> c.decoded('version', 'No Version')
- 'No Version'
-
- The component can render itself in the RFC 2445 format.
- >>> c = Component()
- >>> c.name = 'VCALENDAR'
- >>> c.add('attendee', 'Max M')
- >>> c.as_string()
- 'BEGIN:VCALENDAR\\r\\nATTENDEE:Max M\\r\\nEND:VCALENDAR\\r\\n'
-
- >>> from icalendar.prop import vDatetime
-
- Components can be nested, so You can add a subcompont. Eg a calendar holds events.
- >>> e = Component(summary='A brief history of time')
- >>> e.name = 'VEVENT'
- >>> e.add('dtend', '20000102T000000', encode=0)
- >>> e.add('dtstart', '20000101T000000', encode=0)
- >>> e.as_string()
- 'BEGIN:VEVENT\\r\\nDTEND:20000102T000000\\r\\nDTSTART:20000101T000000\\r\\nSUMMARY:A brief history of time\\r\\nEND:VEVENT\\r\\n'
-
- >>> c.add_component(e)
- >>> c.subcomponents
- [VEVENT({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', 'SUMMARY': 'A brief history of time'})]
-
- We can walk over nested componentes with the walk method.
- >>> [i.name for i in c.walk()]
- ['VCALENDAR', 'VEVENT']
-
- We can also just walk over specific component types, by filtering them on
- their name.
- >>> [i.name for i in c.walk('VEVENT')]
- ['VEVENT']
-
- >>> [i['dtstart'] for i in c.walk('VEVENT')]
- ['20000101T000000']
-
- INLINE properties have their values on one property line. Note the double
- quoting of the value with a colon in it.
- >>> c = Calendar()
- >>> c['resources'] = 'Chair, Table, "Room: 42"'
- >>> c
- VCALENDAR({'RESOURCES': 'Chair, Table, "Room: 42"'})
-
- >>> c.as_string()
- 'BEGIN:VCALENDAR\\r\\nRESOURCES:Chair, Table, "Room: 42"\\r\\nEND:VCALENDAR\\r\\n'
-
- The inline values must be handled by the get_inline() and set_inline()
- methods.
-
- >>> c.get_inline('resources', decode=0)
- ['Chair', 'Table', 'Room: 42']
-
- These can also be decoded
- >>> c.get_inline('resources', decode=1)
- [u'Chair', u'Table', u'Room: 42']
-
- You can set them directly
- >>> c.set_inline('resources', ['A', 'List', 'of', 'some, recources'], encode=1)
- >>> c['resources']
- 'A,List,of,"some, recources"'
-
- and back again
- >>> c.get_inline('resources', decode=0)
- ['A', 'List', 'of', 'some, recources']
-
- >>> c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,19970308T230000Z/19970309T000000Z'
- >>> c.get_inline('freebusy', decode=0)
- ['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', '19970308T230000Z/19970309T000000Z']
-
- >>> freebusy = c.get_inline('freebusy', decode=1)
- >>> type(freebusy[0][0]), type(freebusy[0][1])
- (, )
- """
-
- name = '' # must be defined in each component
- required = () # These properties are required
- singletons = () # These properties must only appear once
- multiple = () # may occur more than once
- exclusive = () # These properties are mutually exclusive
- inclusive = () # if any occurs the other(s) MUST occur ('duration', 'repeat')
-
- def __init__(self, *args, **kwargs):
- "Set keys to upper for initial dict"
- CaselessDict.__init__(self, *args, **kwargs)
- # set parameters here for properties that use non-default values
- self.subcomponents = [] # Components can be nested.
-
-
-# def non_complience(self, warnings=0):
-# """
-# not implemented yet!
-# Returns a dict describing non compliant properties, if any.
-# If warnings is true it also returns warnings.
-#
-# If the parser is too strict it might prevent parsing erroneous but
-# otherwise compliant properties. So the parser is pretty lax, but it is
-# possible to test for non-complience by calling this method.
-# """
-# nc = {}
-# if not getattr(self, 'name', ''):
-# nc['name'] = {'type':'ERROR', 'description':'Name is not defined'}
-# return nc
-
-
- #############################
- # handling of property values
-
- def _encode(self, name, value, cond=1):
- # internal, for conditional convertion of values.
- if cond:
- klass = types_factory.for_property(name)
- return klass(value)
- return value
-
-
- def set(self, name, value, encode=1):
- if type(value) == ListType:
- self[name] = [self._encode(name, v, encode) for v in value]
- else:
- self[name] = self._encode(name, value, encode)
-
-
- def add(self, name, value, encode=1):
- "If property exists append, else create and set it"
- if name in self:
- oldval = self[name]
- value = self._encode(name, value, encode)
- if type(oldval) == ListType:
- oldval.append(value)
- else:
- self.set(name, [oldval, value], encode=0)
- else:
- self.set(name, value, encode)
-
-
- def _decode(self, name, value):
- # internal for decoding property values
- decoded = types_factory.from_ical(name, value)
- return decoded
-
-
- def decoded(self, name, default=_marker):
- "Returns decoded value of property"
- if name in self:
- value = self[name]
- if type(value) == ListType:
- return [self._decode(name, v) for v in value]
- return self._decode(name, value)
- else:
- if default is _marker:
- raise KeyError, name
- else:
- return default
-
-
- ########################################################################
- # Inline values. A few properties have multiple values inlined in in one
- # property line. These methods are used for splitting and joining these.
-
- def get_inline(self, name, decode=1):
- """
- Returns a list of values (split on comma).
- """
- vals = [v.strip('" ').encode(vText.encoding)
- for v in q_split(self[name])]
- if decode:
- return [self._decode(name, val) for val in vals]
- return vals
-
-
- def set_inline(self, name, values, encode=1):
- """
- Converts a list of values into comma seperated string and sets value to
- that.
- """
- if encode:
- values = [self._encode(name, value, 1) for value in values]
- joined = q_join(values).encode(vText.encoding)
- self[name] = types_factory['inline'](joined)
-
-
- #########################
- # Handling of components
-
- def add_component(self, component):
- "add a subcomponent to this component"
- self.subcomponents.append(component)
-
-
- def _walk(self, name):
- # private!
- result = []
- if name is None or self.name == name:
- result.append(self)
- for subcomponent in self.subcomponents:
- result += subcomponent._walk(name)
- return result
-
-
- def walk(self, name=None):
- """
- Recursively traverses component and subcomponents. Returns sequence of
- same. If name is passed, only components with name will be returned.
- """
- if not name is None:
- name = name.upper()
- return self._walk(name)
-
- #####################
- # Generation
-
- def property_items(self):
- """
- Returns properties in this component and subcomponents as:
- [(name, value), ...]
- """
- vText = types_factory['text']
- properties = [('BEGIN', vText(self.name).ical())]
- property_names = self.keys()
- property_names.sort()
- for name in property_names:
- values = self[name]
- if type(values) == ListType:
- # normally one property is one line
- for value in values:
- properties.append((name, value))
- else:
- properties.append((name, values))
- # recursion is fun!
- for subcomponent in self.subcomponents:
- properties += subcomponent.property_items()
- properties.append(('END', vText(self.name).ical()))
- return properties
-
-
- def from_string(st, multiple=False):
- """
- Populates the component recursively from a string
- """
- stack = [] # a stack of components
- comps = []
- for line in Contentlines.from_string(st): # raw parsing
- if not line:
- continue
- name, params, vals = line.parts()
- uname = name.upper()
- # check for start of component
- if uname == 'BEGIN':
- # try and create one of the components defined in the spec,
- # otherwise get a general Components for robustness.
- component_name = vals.upper()
- component_class = component_factory.get(component_name, Component)
- component = component_class()
- if not getattr(component, 'name', ''): # for undefined components
- component.name = component_name
- stack.append(component)
- # check for end of event
- elif uname == 'END':
- # we are done adding properties to this component
- # so pop it from the stack and add it to the new top.
- component = stack.pop()
- if not stack: # we are at the end
- comps.append(component)
- else:
- stack[-1].add_component(component)
- # we are adding properties to the current top of the stack
- else:
- factory = types_factory.for_property(name)
- vals = factory(factory.from_ical(vals))
- vals.params = params
- stack[-1].add(name, vals, encode=0)
- if multiple:
- return comps
- if not len(comps) == 1:
- raise ValueError('Found multiple components where '
- 'only one is allowed')
- return comps[0]
- from_string = staticmethod(from_string)
-
-
- def __repr__(self):
- return '%s(' % self.name + dict.__repr__(self) + ')'
-
-# def content_line(self, name):
-# "Returns property as content line"
-# value = self[name]
-# params = getattr(value, 'params', Parameters())
-# return Contentline.from_parts((name, params, value))
-
- def content_lines(self):
- "Converts the Component and subcomponents into content lines"
- contentlines = Contentlines()
- for name, values in self.property_items():
- params = getattr(values, 'params', Parameters())
- contentlines.append(Contentline.from_parts((name, params, values)))
- contentlines.append('') # remember the empty string in the end
- return contentlines
-
-
- def as_string(self):
- return str(self.content_lines())
-
-
- def __str__(self):
- "Returns rendered iCalendar"
- return self.as_string()
-
-
-
-#######################################
-# components defined in RFC 2445
-
-
-class Event(Component):
-
- name = 'VEVENT'
-
- required = ('UID',)
- singletons = (
- 'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'GEO',
- 'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'DTSTAMP', 'SEQUENCE',
- 'STATUS', 'SUMMARY', 'TRANSP', 'URL', 'RECURID', 'DTEND', 'DURATION',
- 'DTSTART',
- )
- exclusive = ('DTEND', 'DURATION', )
- multiple = (
- 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT','CONTACT', 'EXDATE',
- 'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
- )
-
-
-
-class Todo(Component):
-
- name = 'VTODO'
-
- required = ('UID',)
- singletons = (
- 'CLASS', 'COMPLETED', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART',
- 'GEO', 'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY',
- 'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', 'DUE', 'DURATION',
- )
- exclusive = ('DUE', 'DURATION',)
- multiple = (
- 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
- 'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
- )
-
-
-
-class Journal(Component):
-
- name = 'VJOURNAL'
-
- required = ('UID',)
- singletons = (
- 'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'DTSTAMP', 'LAST-MOD',
- 'ORGANIZER', 'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL',
- )
- multiple = (
- 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
- 'EXRULE', 'RELATED', 'RDATE', 'RRULE', 'RSTATUS',
- )
-
-
-class FreeBusy(Component):
-
- name = 'VFREEBUSY'
-
- required = ('UID',)
- singletons = (
- 'CONTACT', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP', 'ORGANIZER',
- 'UID', 'URL',
- )
- multiple = ('ATTENDEE', 'COMMENT', 'FREEBUSY', 'RSTATUS',)
-
-
-class Timezone(Component):
-
- name = 'VTIMEZONE'
-
- required = (
- 'TZID', 'STANDARDC', 'DAYLIGHTC', 'DTSTART', 'TZOFFSETTO',
- 'TZOFFSETFROM'
- )
- singletons = ('LAST-MOD', 'TZURL', 'TZID',)
- multiple = ('COMMENT', 'RDATE', 'RRULE', 'TZNAME',)
-
-
-class Alarm(Component):
-
- name = 'VALARM'
- # not quite sure about these ...
- required = ('ACTION', 'TRIGGER',)
- singletons = ('ATTACH', 'ACTION', 'TRIGGER', 'DURATION', 'REPEAT',)
- inclusive = (('DURATION', 'REPEAT',),)
- multiple = ('STANDARDC', 'DAYLIGHTC')
-
-
-class Calendar(Component):
- """
- This is the base object for an iCalendar file.
-
- Setting up a minimal calendar component looks like this
- >>> cal = Calendar()
-
- Som properties are required to be compliant
- >>> cal['prodid'] = '-//My calendar product//mxm.dk//'
- >>> cal['version'] = '2.0'
-
- We also need at least one subcomponent for a calendar to be compliant
- >>> from datetime import datetime
- >>> event = Event()
- >>> event['summary'] = 'Python meeting about calendaring'
- >>> event['uid'] = '42'
- >>> event.set('dtstart', datetime(2005,4,4,8,0,0))
- >>> cal.add_component(event)
- >>> cal.subcomponents[0].as_string()
- 'BEGIN:VEVENT\\r\\nDTSTART:20050404T080000\\r\\nSUMMARY:Python meeting about calendaring\\r\\nUID:42\\r\\nEND:VEVENT\\r\\n'
-
- Write to disc
- >>> import tempfile, os
- >>> directory = tempfile.mkdtemp()
- >>> open(os.path.join(directory, 'test.ics'), 'wb').write(cal.as_string())
- """
-
- name = 'VCALENDAR'
- required = ('prodid', 'version', )
- singletons = ('prodid', 'version', )
- multiple = ('calscale', 'method', )
-
-
-# These are read only singleton, so one instance is enough for the module
-types_factory = TypesFactory()
-component_factory = ComponentFactory()
diff --git a/Webcal/icalendar/caselessdict.py b/Webcal/icalendar/caselessdict.py
deleted file mode 100644
index 8d943f7..0000000
--- a/Webcal/icalendar/caselessdict.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# -*- coding: latin-1 -*-
-
-class CaselessDict(dict):
- """
- A dictionary that isn't case sensitive, and only use string as keys.
-
- >>> ncd = CaselessDict(key1='val1', key2='val2')
- >>> ncd
- CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
- >>> ncd['key1']
- 'val1'
- >>> ncd['KEY1']
- 'val1'
- >>> ncd['KEY3'] = 'val3'
- >>> ncd['key3']
- 'val3'
- >>> ncd.setdefault('key3', 'FOUND')
- 'val3'
- >>> ncd.setdefault('key4', 'NOT FOUND')
- 'NOT FOUND'
- >>> ncd['key4']
- 'NOT FOUND'
- >>> ncd.get('key1')
- 'val1'
- >>> ncd.get('key3', 'NOT FOUND')
- 'val3'
- >>> ncd.get('key4', 'NOT FOUND')
- 'NOT FOUND'
- >>> 'key4' in ncd
- True
- >>> del ncd['key4']
- >>> ncd.has_key('key4')
- False
- >>> ncd.update({'key5':'val5', 'KEY6':'val6', 'KEY5':'val7'})
- >>> ncd['key6']
- 'val6'
- >>> keys = ncd.keys()
- >>> keys.sort()
- >>> keys
- ['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6']
- """
-
- def __init__(self, *args, **kwargs):
- "Set keys to upper for initial dict"
- dict.__init__(self, *args, **kwargs)
- for k,v in self.items():
- k_upper = k.upper()
- if k != k_upper:
- dict.__delitem__(self, k)
- self[k_upper] = v
-
- def __getitem__(self, key):
- return dict.__getitem__(self, key.upper())
-
- def __setitem__(self, key, value):
- dict.__setitem__(self, key.upper(), value)
-
- def __delitem__(self, key):
- dict.__delitem__(self, key.upper())
-
- def __contains__(self, item):
- return dict.__contains__(self, item.upper())
-
- def get(self, key, default=None):
- return dict.get(self, key.upper(), default)
-
- def setdefault(self, key, value=None):
- return dict.setdefault(self, key.upper(), value)
-
- def pop(self, key, default=None):
- return dict.pop(self, key.upper(), default)
-
- def popitem(self):
- return dict.popitem(self)
-
- def has_key(self, key):
- return dict.has_key(self, key.upper())
-
- def update(self, indict):
- """
- Multiple keys where key1.upper() == key2.upper() will be lost.
- """
- for entry in indict:
- self[entry] = indict[entry]
-
- def copy(self):
- return CaselessDict(dict.copy(self))
-
- def clear(self):
- dict.clear(self)
-
- def __repr__(self):
- return 'CaselessDict(' + dict.__repr__(self) + ')'
diff --git a/Webcal/icalendar/interfaces.py b/Webcal/icalendar/interfaces.py
deleted file mode 100644
index e30e940..0000000
--- a/Webcal/icalendar/interfaces.py
+++ /dev/null
@@ -1,263 +0,0 @@
-# -*- Encoding: utf-8 -*-
-try:
- from zope.interface import Interface, Attribute
-except ImportError:
- class Interface:
- """A dummy interface base class"""
-
- class Attribute:
- """A dummy attribute implementation"""
- def __init__(self, doc):
- self.doc = doc
-
-_marker = object()
-
-class IComponent(Interface):
- """
- Component is the base object for calendar, Event and the other
- components defined in RFC 2445.
-
- A component is like a dictionary with extra methods and attributes.
- """
-
- # MANIPULATORS
-
- def __setitem__(name, value):
- """Set a property.
-
- name - case insensitive name
- value - value of the property to set. This can be either a single
- item or a list.
-
- Some iCalendar properties are set INLINE; these properties
- have multiple values on one property line in the iCalendar
- representation. The list can be supplied as a comma separated
- string to __setitem__. If special iCalendar characters exist in
- an entry, such as the colon (:) and (,), that comma-separated
- entry needs to be quoted with double quotes. For example:
-
- 'foo, bar, "baz:hoi"'
-
- See also set_inline() for an easier way to deal with this case.
- """
-
- def set_inline(name, values, encode=1):
- """Set list of INLINE values for property.
-
- Converts a list of values into valid iCalendar comma seperated
- string and sets value to that.
-
- name - case insensitive name of property
- values - list of values to set
- encode - if True, encode Python values as iCalendar types first.
- """
-
- def add(name, value):
- """Add a property. Can be called multiple times to set a list.
-
- name - case insensitive name
- value - value of property to set or add to list for this property.
- """
-
- def add_component(component):
- """Add a nested subcomponent to this component.
- """
-
- # static method, can be called on class directly
- def from_string(st, multiple=False):
- """Populates the component recursively from a iCalendar string.
-
- Reads the iCalendar string and constructs components and
- subcomponents out of it.
- """
-
- # ACCESSORS
- def __getitem__(name):
- """Get a property
-
- name - case insensitive name
-
- Returns an iCalendar property object such as vText.
- """
-
- def decoded(name, default=_marker):
- """Get a property as a python object.
-
- name - case insensitive name
- default - optional argument. If supplied, will use this if
- name cannot be found. If not supplied, decoded will raise a
- KeyError if name cannot be found.
-
- Returns python object (such as unicode string, datetime, etc).
- """
-
- def get_inline(name, decode=1):
- """Get list of INLINE values from property.
-
- name - case insensitive name
- decode - decode to Python objects.
-
- Returns list of python objects.
- """
-
- def as_string():
- """Render the component in the RFC 2445 (iCalendar) format.
-
- Returns a string in RFC 2445 format.
- """
-
- subcomponents = Attribute("""
- A list of all subcomponents of this component,
- added using add_component()""")
-
- name = Attribute("""
- Name of this component (VEVENT, etc)
- """)
-
- def walk(name=None):
- """Recursively traverses component and subcomponents.
-
- name - optional, if given, only return components with that name
-
- Returns sequence of components.
- """
-
- def property_items():
- """Return properties as (name, value) tuples.
-
- Returns all properties in this comopnent and subcomponents as
- name, value tuples.
- """
-
-class IEvent(IComponent):
- """A component which conforms to an iCalendar VEVENT.
- """
-
-class ITodo(IComponent):
- """A component which conforms to an iCalendar VTODO.
- """
-
-class IJournal(IComponent):
- """A component which conforms to an iCalendar VJOURNAL.
- """
-
-class IFreeBusy(IComponent):
- """A component which conforms to an iCalendar VFREEBUSY.
- """
-
-class ITimezone(IComponent):
- """A component which conforms to an iCalendar VTIMEZONE.
- """
-
-class IAlarm(IComponent):
- """A component which conforms to an iCalendar VALARM.
- """
-
-class ICalendar(IComponent):
- """A component which conforms to an iCalendar VCALENDAR.
- """
-
-class IPropertyValue(Interface):
- """An iCalendar property value.
- iCalendar properties have strongly typed values.
-
- This invariance should always be true:
-
- assert x == vDataType.from_ical(vDataType(x).ical())
- """
-
- def ical():
- """Render property as string, as defined in iCalendar RFC 2445.
- """
-
- # this is a static method
- def from_ical(ical):
- """Parse property from iCalendar RFC 2445 text.
-
- Inverse of ical().
- """
-
-class IBinary(IPropertyValue):
- """Binary property values are base 64 encoded
- """
-
-class IBoolean(IPropertyValue):
- """Boolean property.
-
- Also behaves like a python int.
- """
-
-class ICalAddress(IPropertyValue):
- """Email address.
-
- Also behaves like a python str.
- """
-
-class IDateTime(IPropertyValue):
- """Render and generates iCalendar datetime format.
-
- Important: if tzinfo is defined it renders itself as 'date with utc time'
- Meaning that it has a 'Z' appended, and is in absolute time.
- """
-
-class IDate(IPropertyValue):
- """Render and generates iCalendar date format.
- """
-
-class IDuration(IPropertyValue):
- """Render and generates timedelta in iCalendar DURATION format.
- """
-
-class IFloat(IPropertyValue):
- """Render and generate floats in iCalendar format.
-
- Also behaves like a python float.
- """
-
-class IInt(IPropertyValue):
- """Render and generate ints in iCalendar format.
-
- Also behaves like a python int.
- """
-
-class IPeriod(IPropertyValue):
- """A precise period of time (datetime, datetime).
- """
-
-class IWeekDay(IPropertyValue):
- """Render and generate weekday abbreviation.
- """
-
-class IFrequency(IPropertyValue):
- """Frequency.
- """
-
-class IRecur(IPropertyValue):
- """Render and generate data based on recurrent event representation.
-
- This acts like a caseless dictionary.
- """
-
-class IText(IPropertyValue):
- """Unicode text.
- """
-
-class ITime(IPropertyValue):
- """Time.
- """
-
-class IUri(IPropertyValue):
- """URI
- """
-
-class IGeo(IPropertyValue):
- """Geographical location.
- """
-
-class IUTCOffset(IPropertyValue):
- """Offset from UTC.
- """
-
-class IInline(IPropertyValue):
- """Inline list.
- """
diff --git a/Webcal/icalendar/parser.py b/Webcal/icalendar/parser.py
deleted file mode 100644
index de695cb..0000000
--- a/Webcal/icalendar/parser.py
+++ /dev/null
@@ -1,522 +0,0 @@
-# -*- coding: latin-1 -*-
-
-"""
-This module parses and generates contentlines as defined in RFC 2445
-(iCalendar), but will probably work for other MIME types with similar syntax.
-Eg. RFC 2426 (vCard)
-
-It is stupid in the sense that it treats the content purely as strings. No type
-conversion is attempted.
-
-Copyright, 2005: Max M
-License: GPL (Just contact med if and why you would like it changed)
-"""
-
-# from python
-from types import TupleType, ListType
-SequenceTypes = [TupleType, ListType]
-import re
-# from this package
-from icalendar.caselessdict import CaselessDict
-
-
-#################################################################
-# Property parameter stuff
-
-def paramVal(val):
- "Returns a parameter value"
- if type(val) in SequenceTypes:
- return q_join(val)
- return dQuote(val)
-
-# Could be improved
-NAME = re.compile('[\w-]+')
-UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]')
-QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]')
-FOLD = re.compile('([\r]?\n)+[ \t]{1}')
-
-def validate_token(name):
- match = NAME.findall(name)
- if len(match) == 1 and name == match[0]:
- return
- raise ValueError, name
-
-def validate_param_value(value, quoted=True):
- validator = UNSAFE_CHAR
- if quoted:
- validator = QUNSAFE_CHAR
- if validator.findall(value):
- raise ValueError, value
-
-QUOTABLE = re.compile('[,;:].')
-def dQuote(val):
- """
- Parameter values containing [,;:] must be double quoted
- >>> dQuote('Max')
- 'Max'
- >>> dQuote('Rasmussen, Max')
- '"Rasmussen, Max"'
- >>> dQuote('name:value')
- '"name:value"'
- """
- if QUOTABLE.search(val):
- return '"%s"' % val
- return val
-
-# parsing helper
-def q_split(st, sep=','):
- """
- Splits a string on char, taking double (q)uotes into considderation
- >>> q_split('Max,Moller,"Rasmussen, Max"')
- ['Max', 'Moller', '"Rasmussen, Max"']
- """
- result = []
- cursor = 0
- length = len(st)
- inquote = 0
- for i in range(length):
- ch = st[i]
- if ch == '"':
- inquote = not inquote
- if not inquote and ch == sep:
- result.append(st[cursor:i])
- cursor = i + 1
- if i + 1 == length:
- result.append(st[cursor:])
- return result
-
-def q_join(lst, sep=','):
- """
- Joins a list on sep, quoting strings with QUOTABLE chars
- >>> s = ['Max', 'Moller', 'Rasmussen, Max']
- >>> q_join(s)
- 'Max,Moller,"Rasmussen, Max"'
- """
- return sep.join([dQuote(itm) for itm in lst])
-
-class Parameters(CaselessDict):
- """
- Parser and generator of Property parameter strings. It knows nothing of
- datatypes. It's main concern is textual structure.
-
-
- Simple parameter:value pair
- >>> p = Parameters(parameter1='Value1')
- >>> str(p)
- 'PARAMETER1=Value1'
-
-
- keys are converted to upper
- >>> p.keys()
- ['PARAMETER1']
-
-
- Parameters are case insensitive
- >>> p['parameter1']
- 'Value1'
- >>> p['PARAMETER1']
- 'Value1'
-
-
- Parameter with list of values must be seperated by comma
- >>> p = Parameters({'parameter1':['Value1', 'Value2']})
- >>> str(p)
- 'PARAMETER1=Value1,Value2'
-
-
- Multiple parameters must be seperated by a semicolon
- >>> p = Parameters({'RSVP':'TRUE', 'ROLE':'REQ-PARTICIPANT'})
- >>> str(p)
- 'ROLE=REQ-PARTICIPANT;RSVP=TRUE'
-
-
- Parameter values containing ',;:' must be double quoted
- >>> p = Parameters({'ALTREP':'http://www.wiz.org'})
- >>> str(p)
- 'ALTREP="http://www.wiz.org"'
-
-
- list items must be quoted seperately
- >>> p = Parameters({'MEMBER':['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com', ]})
- >>> str(p)
- 'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
-
- Now the whole sheebang
- >>> p = Parameters({'parameter1':'Value1', 'parameter2':['Value2', 'Value3'],\
- 'ALTREP':['http://www.wiz.org', 'value4']})
- >>> str(p)
- 'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'
-
- We can also parse parameter strings
- >>> Parameters.from_string('PARAMETER1=Value 1;param2=Value 2')
- Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'})
-
- Including empty strings
- >>> Parameters.from_string('param=')
- Parameters({'PARAM': ''})
-
- We can also parse parameter strings
- >>> Parameters.from_string('MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"')
- Parameters({'MEMBER': ['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com']})
-
- We can also parse parameter strings
- >>> Parameters.from_string('ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3')
- Parameters({'PARAMETER1': 'Value1', 'ALTREP': ['http://www.wiz.org', 'value4'], 'PARAMETER2': ['Value2', 'Value3']})
- """
-
-
- def params(self):
- """
- in rfc2445 keys are called parameters, so this is to be consitent with
- the naming conventions
- """
- return self.keys()
-
-### Later, when I get more time... need to finish this off now. The last majot thing missing.
-### def _encode(self, name, value, cond=1):
-### # internal, for conditional convertion of values.
-### if cond:
-### klass = types_factory.for_property(name)
-### return klass(value)
-### return value
-###
-### def add(self, name, value, encode=0):
-### "Add a parameter value and optionally encode it."
-### if encode:
-### value = self._encode(name, value, encode)
-### self[name] = value
-###
-### def decoded(self, name):
-### "returns a decoded value, or list of same"
-
- def __repr__(self):
- return 'Parameters(' + dict.__repr__(self) + ')'
-
-
- def __str__(self):
- result = []
- items = self.items()
- items.sort() # To make doctests work
- for key, value in items:
- value = paramVal(value)
- result.append('%s=%s' % (key.upper(), value))
- return ';'.join(result)
-
-
- def from_string(st, strict=False):
- "Parses the parameter format from ical text format"
- try:
- # parse into strings
- result = Parameters()
- for param in q_split(st, ';'):
- key, val = q_split(param, '=')
- validate_token(key)
- param_values = [v for v in q_split(val, ',')]
- # Property parameter values that are not in quoted
- # strings are case insensitive.
- vals = []
- for v in param_values:
- if v.startswith('"') and v.endswith('"'):
- v = v.strip('"')
- validate_param_value(v, quoted=True)
- vals.append(v)
- else:
- validate_param_value(v, quoted=False)
- if strict:
- vals.append(v.upper())
- else:
- vals.append(v)
- if not vals:
- result[key] = val
- else:
- if len(vals) == 1:
- result[key] = vals[0]
- else:
- result[key] = vals
- return result
- except:
- raise ValueError, 'Not a valid parameter string'
- from_string = staticmethod(from_string)
-
-
-#########################################
-# parsing and generation of content lines
-
-class Contentline(str):
- """
- A content line is basically a string that can be folded and parsed into
- parts.
-
- >>> c = Contentline('Si meliora dies, ut vina, poemata reddit')
- >>> str(c)
- 'Si meliora dies, ut vina, poemata reddit'
-
- A long line gets folded
- >>> c = Contentline(''.join(['123456789 ']*10))
- >>> str(c)
- '123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 '
-
- A folded line gets unfolded
- >>> c = Contentline.from_string(str(c))
- >>> c
- '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 '
-
- We do not fold within a UTF-8 character:
- >>> c = Contentline('This line has a UTF-8 character where it should be folded. Make sure it g\xc3\xabts folded before that character.')
- >>> '\xc3\xab' in str(c)
- True
-
- Don't fail if we fold a line that is exactly X times 74 characters long:
- >>> c = str(Contentline(''.join(['x']*148)))
-
- It can parse itself into parts. Which is a tuple of (name, params, vals)
-
- >>> c = Contentline('dtstart:20050101T120000')
- >>> c.parts()
- ('dtstart', Parameters({}), '20050101T120000')
-
- >>> c = Contentline('dtstart;value=datetime:20050101T120000')
- >>> c.parts()
- ('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
-
- >>> c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com')
- >>> c.parts()
- ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com')
- >>> str(c)
- 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com'
-
- and back again
- >>> parts = ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com')
- >>> Contentline.from_parts(parts)
- 'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com'
-
- and again
- >>> parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
- >>> Contentline.from_parts(parts)
- 'ATTENDEE:MAILTO:maxm@example.com'
-
- A value can also be any of the types defined in PropertyValues
- >>> from icalendar.prop import vText
- >>> parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com'))
- >>> Contentline.from_parts(parts)
- 'ATTENDEE:MAILTO:test@example.com'
-
- A value can also be unicode
- >>> from icalendar.prop import vText
- >>> parts = ('SUMMARY', Parameters(), vText(u'INternational char æ ø å'))
- >>> Contentline.from_parts(parts)
- 'SUMMARY:INternational char \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5'
-
- Traversing could look like this.
- >>> name, params, vals = c.parts()
- >>> name
- 'ATTENDEE'
- >>> vals
- 'MAILTO:maxm@example.com'
- >>> for key, val in params.items():
- ... (key, val)
- ('ROLE', 'REQ-PARTICIPANT')
- ('CN', 'Max Rasmussen')
-
- And the traditional failure
- >>> c = Contentline('ATTENDEE;maxm@example.com')
- >>> c.parts()
- Traceback (most recent call last):
- ...
- ValueError: Content line could not be parsed into parts
-
- Another failure:
- >>> c = Contentline(':maxm@example.com')
- >>> c.parts()
- Traceback (most recent call last):
- ...
- ValueError: Content line could not be parsed into parts
-
- >>> c = Contentline('key;param=:value')
- >>> c.parts()
- ('key', Parameters({'PARAM': ''}), 'value')
-
- >>> c = Contentline('key;param="pvalue":value')
- >>> c.parts()
- ('key', Parameters({'PARAM': 'pvalue'}), 'value')
-
- Should bomb on missing param:
- >>> c = Contentline.from_string("k;:no param")
- >>> c.parts()
- Traceback (most recent call last):
- ...
- ValueError: Content line could not be parsed into parts
-
- >>> c = Contentline('key;param=pvalue:value', strict=False)
- >>> c.parts()
- ('key', Parameters({'PARAM': 'pvalue'}), 'value')
-
- If strict is set to True, uppercase param values that are not
- double-quoted, this is because the spec says non-quoted params are
- case-insensitive.
-
- >>> c = Contentline('key;param=pvalue:value', strict=True)
- >>> c.parts()
- ('key', Parameters({'PARAM': 'PVALUE'}), 'value')
-
- >>> c = Contentline('key;param="pValue":value', strict=True)
- >>> c.parts()
- ('key', Parameters({'PARAM': 'pValue'}), 'value')
-
- """
-
- def __new__(cls, st, strict=False):
- self = str.__new__(cls, st)
- setattr(self, 'strict', strict)
- return self
-
- def from_parts(parts):
- "Turns a tuple of parts into a content line"
- (name, params, values) = [str(p) for p in parts]
- try:
- if params:
- return Contentline('%s;%s:%s' % (name, params, values))
- return Contentline('%s:%s' % (name, values))
- except:
- raise ValueError(
- 'Property: %s Wrong values "%s" or "%s"' % (repr(name),
- repr(params),
- repr(values)))
- from_parts = staticmethod(from_parts)
-
- def parts(self):
- """ Splits the content line up into (name, parameters, values) parts
- """
- try:
- name_split = None
- value_split = None
- inquotes = 0
- for i in range(len(self)):
- ch = self[i]
- if not inquotes:
- if ch in ':;' and not name_split:
- name_split = i
- if ch == ':' and not value_split:
- value_split = i
- if ch == '"':
- inquotes = not inquotes
- name = self[:name_split]
- if not name:
- raise ValueError, 'Key name is required'
- validate_token(name)
- if name_split+1 == value_split:
- raise ValueError, 'Invalid content line'
- params = Parameters.from_string(self[name_split+1:value_split],
- strict=self.strict)
- values = self[value_split+1:]
- return (name, params, values)
- except:
- raise ValueError, 'Content line could not be parsed into parts'
-
- def from_string(st, strict=False):
- "Unfolds the content lines in an iCalendar into long content lines"
- try:
- # a fold is carriage return followed by either a space or a tab
- return Contentline(FOLD.sub('', st), strict=strict)
- except:
- raise ValueError, 'Expected StringType with content line'
- from_string = staticmethod(from_string)
-
- def __str__(self):
- "Long content lines are folded so they are less than 75 characters wide"
- l_line = len(self)
- new_lines = []
- start = 0
- end = 74
- while True:
- if end >= l_line:
- end = l_line
- else:
- # Check that we don't fold in the middle of a UTF-8 character:
- # http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html
- while True:
- char_value = ord(self[end])
- if char_value < 128 or char_value >= 192:
- # This is not in the middle of a UTF-8 character, so we
- # can fold here:
- break
- else:
- end -= 1
-
- new_lines.append(self[start:end])
- if end == l_line:
- # Done
- break
- start = end
- end = start + 74
- return '\r\n '.join(new_lines)
-
-
-
-class Contentlines(list):
- """
- I assume that iCalendar files generally are a few kilobytes in size. Then
- this should be efficient. for Huge files, an iterator should probably be
- used instead.
-
- >>> c = Contentlines([Contentline('BEGIN:VEVENT\\r\\n')])
- >>> str(c)
- 'BEGIN:VEVENT\\r\\n'
-
- Lets try appending it with a 100 charater wide string
- >>> c.append(Contentline(''.join(['123456789 ']*10)+'\\r\\n'))
- >>> str(c)
- 'BEGIN:VEVENT\\r\\n\\r\\n123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 \\r\\n'
-
- Notice that there is an extra empty string in the end of the content lines.
- That is so they can be easily joined with: '\r\n'.join(contentlines)).
- >>> Contentlines.from_string('A short line\\r\\n')
- ['A short line', '']
- >>> Contentlines.from_string('A faked\\r\\n long line\\r\\n')
- ['A faked long line', '']
- >>> Contentlines.from_string('A faked\\r\\n long line\\r\\nAnd another lin\\r\\n\\te that is folded\\r\\n')
- ['A faked long line', 'And another line that is folded', '']
- """
-
- def __str__(self):
- "Simply join self."
- return '\r\n'.join(map(str, self))
-
- def from_string(st):
- "Parses a string into content lines"
- try:
- # a fold is carriage return followed by either a space or a tab
- unfolded = FOLD.sub('', st)
- lines = [Contentline(line) for line in unfolded.splitlines() if line]
- lines.append('') # we need a '\r\n' in the end of every content line
- return Contentlines(lines)
- except:
- raise ValueError, 'Expected StringType with content lines'
- from_string = staticmethod(from_string)
-
-
-# ran this:
-# sample = open('./samples/test.ics', 'rb').read() # binary file in windows!
-# lines = Contentlines.from_string(sample)
-# for line in lines[:-1]:
-# print line.parts()
-
-# got this:
-#('BEGIN', Parameters({}), 'VCALENDAR')
-#('METHOD', Parameters({}), 'Request')
-#('PRODID', Parameters({}), '-//My product//mxm.dk/')
-#('VERSION', Parameters({}), '2.0')
-#('BEGIN', Parameters({}), 'VEVENT')
-#('DESCRIPTION', Parameters({}), 'This is a very long description that ...')
-#('PARTICIPANT', Parameters({'CN': 'Max M'}), 'MAILTO:maxm@mxm.dk')
-#('DTEND', Parameters({}), '20050107T160000')
-#('DTSTART', Parameters({}), '20050107T120000')
-#('SUMMARY', Parameters({}), 'A second event')
-#('END', Parameters({}), 'VEVENT')
-#('BEGIN', Parameters({}), 'VEVENT')
-#('DTEND', Parameters({}), '20050108T235900')
-#('DTSTART', Parameters({}), '20050108T230000')
-#('SUMMARY', Parameters({}), 'A single event')
-#('UID', Parameters({}), '42')
-#('END', Parameters({}), 'VEVENT')
-#('END', Parameters({}), 'VCALENDAR')
diff --git a/Webcal/icalendar/prop.py b/Webcal/icalendar/prop.py
deleted file mode 100644
index 61aa3fd..0000000
--- a/Webcal/icalendar/prop.py
+++ /dev/null
@@ -1,1513 +0,0 @@
-# -*- coding: latin-1 -*-
-
-"""
-
-This module contains the parser/generators (or coders/encoders if you prefer)
-for the classes/datatypes that are used in Icalendar:
-
-###########################################################################
-# This module defines these property value data types and property parameters
-
-4.2 Defined property parameters are:
-
- ALTREP, CN, CUTYPE, DELEGATED-FROM, DELEGATED-TO, DIR, ENCODING, FMTTYPE,
- FBTYPE, LANGUAGE, MEMBER, PARTSTAT, RANGE, RELATED, RELTYPE, ROLE, RSVP,
- SENT-BY, TZID, VALUE
-
-4.3 Defined value data types are:
-
- BINARY, BOOLEAN, CAL-ADDRESS, DATE, DATE-TIME, DURATION, FLOAT, INTEGER,
- PERIOD, RECUR, TEXT, TIME, URI, UTC-OFFSET
-
-###########################################################################
-
-
-iCalendar properties has values. The values are strongly typed. This module
-defines these types, calling val.ical() on them, Will render them as defined in
-rfc2445.
-
-If you pass any of these classes a Python primitive, you will have an object
-that can render itself as iCalendar formatted date.
-
-Property Value Data Types starts with a 'v'. they all have an ical() and
-from_ical() method. The ical() method generates a text string in the iCalendar
-format. The from_ical() method can parse this format and return a primitive
-Python datatype. So it should allways be true that:
-
- x == vDataType.from_ical(VDataType(x).ical())
-
-These types are mainly used for parsing and file generation. But you can set
-them directly.
-
-"""
-
-# from python >= 2.3
-from datetime import datetime, timedelta, time, date, tzinfo
-from types import IntType, StringType, UnicodeType, TupleType, ListType
-SequenceTypes = [TupleType, ListType]
-import re
-import time as _time
-
-# from this package
-from icalendar.caselessdict import CaselessDict
-from icalendar.parser import Parameters
-
-DATE_PART = r'(\d+)D'
-TIME_PART = r'T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?'
-DATETIME_PART = '(?:%s)?(?:%s)?' % (DATE_PART, TIME_PART)
-WEEKS_PART = r'(\d+)W'
-DURATION_REGEX = re.compile(r'([-+]?)P(?:%s|%s)$'
- % (WEEKS_PART, DATETIME_PART))
-WEEKDAY_RULE = re.compile('(?P[+-]?)(?P[\d]?)'
- '(?P[\w]{2})$')
-
-class vBinary:
- """
- Binary property values are base 64 encoded
- >>> b = vBinary('This is gibberish')
- >>> b.ical()
- 'VGhpcyBpcyBnaWJiZXJpc2g='
- >>> b = vBinary.from_ical('VGhpcyBpcyBnaWJiZXJpc2g=')
- >>> b
- 'This is gibberish'
-
- The roundtrip test
- >>> x = 'Binary data æ ø å \x13 \x56'
- >>> vBinary(x).ical()
- 'QmluYXJ5IGRhdGEg5iD4IOUgEyBW'
- >>> vBinary.from_ical('QmluYXJ5IGRhdGEg5iD4IOUgEyBW')
- 'Binary data \\xe6 \\xf8 \\xe5 \\x13 V'
-
- >>> b = vBinary('txt')
- >>> b.params
- Parameters({'VALUE': 'BINARY', 'ENCODING': 'BASE64'})
- """
-
- def __init__(self, obj):
- self.obj = obj
- self.params = Parameters(encoding='BASE64', value="BINARY")
-
- def __repr__(self):
- return "vBinary(%s)" % str.__repr__(self.obj)
-
- def ical(self):
- return self.obj.encode('base-64')[:-1]
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return ical.decode('base-64')
- except:
- raise ValueError, 'Not valid base 64 encoding.'
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vBoolean(int):
- """
- Returns specific string according to state
- >>> bin = vBoolean(True)
- >>> bin.ical()
- 'TRUE'
- >>> bin = vBoolean(0)
- >>> bin.ical()
- 'FALSE'
-
- The roundtrip test
- >>> x = True
- >>> x == vBoolean.from_ical(vBoolean(x).ical())
- True
- >>> vBoolean.from_ical('true')
- True
- """
-
- def __init__(self, *args, **kwargs):
- int.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def ical(self):
- if self:
- return 'TRUE'
- return 'FALSE'
-
- bool_map = CaselessDict(true=True, false=False)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return vBoolean.bool_map[ical]
- except:
- raise ValueError, "Expected 'TRUE' or 'FALSE'. Got %s" % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vCalAddress(str):
- """
- This just returns an unquoted string
- >>> a = vCalAddress('MAILTO:maxm@mxm.dk')
- >>> a.params['cn'] = 'Max M'
- >>> a.ical()
- 'MAILTO:maxm@mxm.dk'
- >>> str(a)
- 'MAILTO:maxm@mxm.dk'
- >>> a.params
- Parameters({'CN': 'Max M'})
- >>> vCalAddress.from_ical('MAILTO:maxm@mxm.dk')
- 'MAILTO:maxm@mxm.dk'
- """
-
- def __init__(self, *args, **kwargs):
- str.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def __repr__(self):
- return u"vCalAddress(%s)" % str.__repr__(self)
-
- def ical(self):
- return str(self)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return str(ical)
- except:
- raise ValueError, 'Expected vCalAddress, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return str.__str__(self)
-
-####################################################
-# handy tzinfo classes you can use.
-
-ZERO = timedelta(0)
-HOUR = timedelta(hours=1)
-STDOFFSET = timedelta(seconds = -_time.timezone)
-if _time.daylight:
- DSTOFFSET = timedelta(seconds = -_time.altzone)
-else:
- DSTOFFSET = STDOFFSET
-DSTDIFF = DSTOFFSET - STDOFFSET
-
-
-class FixedOffset(tzinfo):
- """Fixed offset in minutes east from UTC."""
-
- def __init__(self, offset, name):
- self.__offset = timedelta(minutes = offset)
- self.__name = name
-
- def utcoffset(self, dt):
- return self.__offset
-
- def tzname(self, dt):
- return self.__name
-
- def dst(self, dt):
- return ZERO
-
-
-class Utc(tzinfo):
- """UTC tzinfo subclass"""
-
- def utcoffset(self, dt):
- return ZERO
-
- def tzname(self, dt):
- return "UTC"
-
- def dst(self, dt):
- return ZERO
-UTC = Utc()
-
-class LocalTimezone(tzinfo):
- """
- Timezone of the machine where the code is running
- """
-
- def utcoffset(self, dt):
- if self._isdst(dt):
- return DSTOFFSET
- else:
- return STDOFFSET
-
- def dst(self, dt):
- if self._isdst(dt):
- return DSTDIFF
- else:
- return ZERO
-
- def tzname(self, dt):
- return _time.tzname[self._isdst(dt)]
-
- def _isdst(self, dt):
- tt = (dt.year, dt.month, dt.day,
- dt.hour, dt.minute, dt.second,
- dt.weekday(), 0, -1)
- stamp = _time.mktime(tt)
- tt = _time.localtime(stamp)
- return tt.tm_isdst > 0
-
-####################################################
-
-
-
-class vDatetime:
- """
- Render and generates iCalendar datetime format.
-
- Important: if tzinfo is defined it renders itself as "date with utc time"
- Meaning that it has a 'Z' appended, and is in absolute time.
-
- >>> d = datetime(2001, 1,1, 12, 30, 0)
-
- >>> dt = vDatetime(d)
- >>> dt.ical()
- '20010101T123000'
-
- >>> vDatetime.from_ical('20000101T120000')
- datetime.datetime(2000, 1, 1, 12, 0)
-
- >>> dutc = datetime(2001, 1,1, 12, 30, 0, tzinfo=UTC)
- >>> vDatetime(dutc).ical()
- '20010101T123000Z'
-
- >>> vDatetime.from_ical('20010101T000000')
- datetime.datetime(2001, 1, 1, 0, 0)
-
- >>> vDatetime.from_ical('20010101T000000A')
- Traceback (most recent call last):
- ...
- ValueError: Wrong datetime format: 20010101T000000A
-
- >>> utc = vDatetime.from_ical('20010101T000000Z')
- >>> vDatetime(utc).ical()
- '20010101T000000Z'
- """
-
- def __init__(self, dt):
- self.dt = dt
- self.params = Parameters()
-
- def ical(self):
- if self.dt.tzinfo:
- offset = self.dt.tzinfo.utcoffset(datetime.now())
- utc_time = self.dt - self.dt.tzinfo.utcoffset(datetime.now())
- return utc_time.strftime("%Y%m%dT%H%M%SZ")
- return self.dt.strftime("%Y%m%dT%H%M%S")
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- timetuple = map(int, ((
- ical[:4], # year
- ical[4:6], # month
- ical[6:8], # day
- ical[9:11], # hour
- ical[11:13], # minute
- ical[13:15], # second
- )))
- if not ical[15:]:
- return datetime(*timetuple)
- elif ical[15:16] == 'Z':
- timetuple += [0, UTC]
- return datetime(*timetuple)
- else:
- raise ValueError, ical
- except:
- raise ValueError, 'Wrong datetime format: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vDate:
- """
- Render and generates iCalendar date format.
- >>> d = date(2001, 1,1)
- >>> vDate(d).ical()
- '20010101'
-
- >>> vDate.from_ical('20010102')
- datetime.date(2001, 1, 2)
-
- >>> vDate('d').ical()
- Traceback (most recent call last):
- ...
- ValueError: Value MUST be a date instance
- """
-
- def __init__(self, dt):
- if not isinstance(dt, date):
- raise ValueError('Value MUST be a date instance')
- self.dt = dt
- self.params = Parameters()
-
- def ical(self):
- return self.dt.strftime("%Y%m%d")
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- timetuple = map(int, ((
- ical[:4], # year
- ical[4:6], # month
- ical[6:8], # day
- )))
- return date(*timetuple)
- except:
- raise ValueError, 'Wrong date format %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vDuration:
- """
- Subclass of timedelta that renders itself in the iCalendar DURATION format.
-
- >>> vDuration(timedelta(11)).ical()
- 'P11D'
- >>> vDuration(timedelta(-14)).ical()
- '-P14D'
- >>> vDuration(timedelta(1, 7384)).ical()
- 'P1DT2H3M4S'
- >>> vDuration(timedelta(1, 7380)).ical()
- 'P1DT2H3M'
- >>> vDuration(timedelta(1, 7200)).ical()
- 'P1DT2H'
- >>> vDuration(timedelta(0, 7200)).ical()
- 'PT2H'
- >>> vDuration(timedelta(0, 7384)).ical()
- 'PT2H3M4S'
- >>> vDuration(timedelta(0, 184)).ical()
- 'PT3M4S'
- >>> vDuration(timedelta(0, 22)).ical()
- 'PT22S'
- >>> vDuration(timedelta(0, 3622)).ical()
- 'PT1H0M22S'
-
- >>> vDuration(timedelta(days=1, hours=5)).ical()
- 'P1DT5H'
- >>> vDuration(timedelta(hours=-5)).ical()
- '-PT5H'
- >>> vDuration(timedelta(days=-1, hours=-5)).ical()
- '-P1DT5H'
-
- How does the parsing work?
- >>> vDuration.from_ical('PT1H0M22S')
- datetime.timedelta(0, 3622)
-
- >>> vDuration.from_ical('kox')
- Traceback (most recent call last):
- ...
- ValueError: Invalid iCalendar duration: kox
-
- >>> vDuration.from_ical('-P14D')
- datetime.timedelta(-14)
-
- >>> vDuration(11)
- Traceback (most recent call last):
- ...
- ValueError: Value MUST be a timedelta instance
- """
-
- def __init__(self, td):
- if not isinstance(td, timedelta):
- raise ValueError('Value MUST be a timedelta instance')
- self.td = td
- self.params = Parameters()
-
- def ical(self):
- sign = ""
- if self.td.days < 0:
- sign = "-"
- self.td = -self.td
- timepart = ""
- if self.td.seconds:
- timepart = "T"
- hours = self.td.seconds // 3600
- minutes = self.td.seconds % 3600 // 60
- seconds = self.td.seconds % 60
- if hours:
- timepart += "%dH" % hours
- if minutes or (hours and seconds):
- timepart += "%dM" % minutes
- if seconds:
- timepart += "%dS" % seconds
- if self.td.days == 0 and timepart:
- return "%sP%s" % (sign, timepart)
- else:
- return "%sP%dD%s" % (sign, abs(self.td.days), timepart)
-
- def from_ical(ical):
- """
- Parses the data format from ical text format.
- """
- try:
- match = DURATION_REGEX.match(ical)
- sign, weeks, days, hours, minutes, seconds = match.groups()
- if weeks:
- value = timedelta(weeks=int(weeks))
- else:
- value = timedelta(days=int(days or 0),
- hours=int(hours or 0),
- minutes=int(minutes or 0),
- seconds=int(seconds or 0))
- if sign == '-':
- value = -value
- return value
- except:
- raise ValueError('Invalid iCalendar duration: %s' % ical)
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vFloat(float):
- """
- Just a float.
- >>> f = vFloat(1.0)
- >>> f.ical()
- '1.0'
- >>> vFloat.from_ical('42')
- 42.0
- >>> vFloat(42).ical()
- '42.0'
- """
-
- def __init__(self, *args, **kwargs):
- float.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def ical(self):
- return str(self)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return float(ical)
- except:
- raise ValueError, 'Expected float value, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
-
-
-class vInt(int):
- """
- Just an int.
- >>> f = vInt(42)
- >>> f.ical()
- '42'
- >>> vInt.from_ical('13')
- 13
- >>> vInt.from_ical('1s3')
- Traceback (most recent call last):
- ...
- ValueError: Expected int, got: 1s3
- """
-
- def __init__(self, *args, **kwargs):
- int.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def ical(self):
- return str(self)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return int(ical)
- except:
- raise ValueError, 'Expected int, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
-
-
-class vDDDTypes:
- """
- A combined Datetime, Date or Duration parser/generator. Their format cannot
- be confused, and often values can be of either types. So this is practical.
-
- >>> d = vDDDTypes.from_ical('20010101T123000')
- >>> type(d)
-
-
- >>> repr(vDDDTypes.from_ical('20010101T123000Z'))[:65]
- 'datetime.datetime(2001, 1, 1, 12, 30, tzinfo=>> d = vDDDTypes.from_ical('20010101')
- >>> type(d)
-
-
- >>> vDDDTypes.from_ical('P31D')
- datetime.timedelta(31)
-
- >>> vDDDTypes.from_ical('-P31D')
- datetime.timedelta(-31)
-
- Bad input
- >>> vDDDTypes(42)
- Traceback (most recent call last):
- ...
- ValueError: You must use datetime, date or timedelta
- """
-
- def __init__(self, dt):
- "Returns vDate from"
- wrong_type_used = 1
- for typ in (datetime, date, timedelta):
- if isinstance(dt, typ):
- wrong_type_used = 0
- if wrong_type_used:
- raise ValueError ('You must use datetime, date or timedelta')
- self.dt = dt
-
- def ical(self):
- dt = self.dt
- if isinstance(dt, datetime):
- return vDatetime(dt).ical()
- elif isinstance(dt, date):
- return vDate(dt).ical()
- elif isinstance(dt, timedelta):
- return vDuration(dt).ical()
- else:
- raise ValueEror ('Unknown date type')
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- u = ical.upper()
- if u.startswith('-P') or u.startswith('P'):
- return vDuration.from_ical(ical)
- try:
- return vDatetime.from_ical(ical)
- except:
- return vDate.from_ical(ical)
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-class vDDDLists:
- """
- A list of vDDDTypes values.
-
- >>> dt_list = vDDDLists.from_ical('19960402T010000Z')
- >>> type(dt_list)
-
-
- >>> len(dt_list)
- 1
-
- >>> type(dt_list[0])
-
-
- >>> str(dt_list[0])
- '1996-04-02 01:00:00+00:00'
-
- >>> dt_list = vDDDLists.from_ical('19960402T010000Z,19960403T010000Z,19960404T010000Z')
- >>> len(dt_list)
- 3
-
- >>> str(dt_list[0])
- '1996-04-02 01:00:00+00:00'
- >>> str(dt_list[2])
- '1996-04-04 01:00:00+00:00'
-
- >>> dt_list = vDDDLists('19960402T010000Z')
- Traceback (most recent call last):
- ...
- ValueError: Value MUST be a list (of date instances)
-
- >>> dt_list = vDDDLists([])
- >>> str(dt_list)
- ''
-
- >>> dt_list = vDDDLists([datetime(2000,1,1)])
- >>> str(dt_list)
- '20000101T000000'
-
- >>> dt_list = vDDDLists([datetime(2000,1,1), datetime(2000,11,11)])
- >>> str(dt_list)
- '20000101T000000,20001111T000000'
- """
-
- def __init__(self, dt_list):
- if not isinstance(dt_list, list):
- raise ValueError('Value MUST be a list (of date instances)')
- vDDD = []
- for dt in dt_list:
- vDDD.append(vDDDTypes(dt))
- self.dts = vDDD
-
- def ical(self):
- '''
- Generates the text string in the iCalendar format.
- '''
- dts_ical = [dt.ical() for dt in self.dts]
- return ",".join(dts_ical)
-
- def from_ical(ical):
- '''
- Parses the list of data formats from ical text format.
- @param ical: ical text format
- '''
- out = []
- ical_dates = ical.split(",")
- for ical_dt in ical_dates:
- out.append(vDDDTypes.from_ical(ical_dt))
- return out
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-class vPeriod:
- """
- A precise period of time.
- One day in exact datetimes
- >>> per = (datetime(2000,1,1), datetime(2000,1,2))
- >>> p = vPeriod(per)
- >>> p.ical()
- '20000101T000000/20000102T000000'
-
- >>> per = (datetime(2000,1,1), timedelta(days=31))
- >>> p = vPeriod(per)
- >>> p.ical()
- '20000101T000000/P31D'
-
- Roundtrip
- >>> p = vPeriod.from_ical('20000101T000000/20000102T000000')
- >>> p
- (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0))
- >>> vPeriod(p).ical()
- '20000101T000000/20000102T000000'
-
- >>> vPeriod.from_ical('20000101T000000/P31D')
- (datetime.datetime(2000, 1, 1, 0, 0), datetime.timedelta(31))
-
- Roundtrip with absolute time
- >>> p = vPeriod.from_ical('20000101T000000Z/20000102T000000Z')
- >>> vPeriod(p).ical()
- '20000101T000000Z/20000102T000000Z'
-
- And an error
- >>> vPeriod.from_ical('20000101T000000/Psd31D')
- Traceback (most recent call last):
- ...
- ValueError: Expected period format, got: 20000101T000000/Psd31D
-
- Utc datetime
- >>> da_tz = FixedOffset(+1.0, 'da_DK')
- >>> start = datetime(2000,1,1, tzinfo=da_tz)
- >>> end = datetime(2000,1,2, tzinfo=da_tz)
- >>> per = (start, end)
- >>> vPeriod(per).ical()
- '19991231T235900Z/20000101T235900Z'
-
- >>> p = vPeriod((datetime(2000,1,1, tzinfo=da_tz), timedelta(days=31)))
- >>> p.ical()
- '19991231T235900Z/P31D'
- """
-
- def __init__(self, per):
- start, end_or_duration = per
- if not (isinstance(start, datetime) or isinstance(start, date)):
- raise ValueError('Start value MUST be a datetime or date instance')
- if not (isinstance(end_or_duration, datetime) or
- isinstance(end_or_duration, date) or
- isinstance(end_or_duration, timedelta)):
- raise ValueError('end_or_duration MUST be a datetime, date or timedelta instance')
- self.start = start
- self.end_or_duration = end_or_duration
- self.by_duration = 0
- if isinstance(end_or_duration, timedelta):
- self.by_duration = 1
- self.duration = end_or_duration
- self.end = self.start + self.duration
- else:
- self.end = end_or_duration
- self.duration = self.end - self.start
- if self.start > self.end:
- raise ValueError("Start time is greater than end time")
- self.params = Parameters()
-
- def __cmp__(self, other):
- if not isinstance(other, vPeriod):
- raise NotImplementedError(
- 'Cannot compare vPeriod with %s' % repr(other))
- return cmp((self.start, self.end), (other.start, other.end))
-
- def overlaps(self, other):
- if self.start > other.start:
- return other.overlaps(self)
- if self.start <= other.start < self.end:
- return True
- return False
-
- def ical(self):
- if self.by_duration:
- return '%s/%s' % (vDatetime(self.start).ical(), vDuration(self.duration).ical())
- return '%s/%s' % (vDatetime(self.start).ical(), vDatetime(self.end).ical())
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- start, end_or_duration = ical.split('/')
- start = vDDDTypes.from_ical(start)
- end_or_duration = vDDDTypes.from_ical(end_or_duration)
- return (start, end_or_duration)
- except:
- raise ValueError, 'Expected period format, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
- def __repr__(self):
- if self.by_duration:
- p = (self.start, self.duration)
- else:
- p = (self.start, self.end)
- return 'vPeriod(%s)' % repr(p)
-
-class vWeekday(str):
- """
- This returns an unquoted weekday abbrevation
- >>> a = vWeekday('mo')
- >>> a.ical()
- 'MO'
-
- >>> a = vWeekday('erwer')
- Traceback (most recent call last):
- ...
- ValueError: Expected weekday abbrevation, got: ERWER
-
- >>> vWeekday.from_ical('mo')
- 'MO'
-
- >>> vWeekday.from_ical('+3mo')
- '+3MO'
-
- >>> vWeekday.from_ical('Saturday')
- Traceback (most recent call last):
- ...
- ValueError: Expected weekday abbrevation, got: Saturday
-
- >>> a = vWeekday('+mo')
- >>> a.ical()
- '+MO'
-
- >>> a = vWeekday('+3mo')
- >>> a.ical()
- '+3MO'
-
- >>> a = vWeekday('-tu')
- >>> a.ical()
- '-TU'
- """
-
- week_days = CaselessDict({"SU":0, "MO":1, "TU":2, "WE":3,
- "TH":4, "FR":5, "SA":6})
-
- def __init__(self, *args, **kwargs):
- str.__init__(self, *args, **kwargs)
- match = WEEKDAY_RULE.match(self)
- if match is None:
- raise ValueError, 'Expected weekday abbrevation, got: %s' % self
- match = match.groupdict()
- sign = match['signal']
- weekday = match['weekday']
- relative = match['relative']
- if not weekday in vWeekday.week_days or sign not in '+-':
- raise ValueError, 'Expected weekday abbrevation, got: %s' % self
- self.relative = relative and int(relative) or None
- self.params = Parameters()
-
- def ical(self):
- return self.upper()
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return vWeekday(ical.upper())
- except:
- raise ValueError, 'Expected weekday abbrevation, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vFrequency(str):
- """
- A simple class that catches illegal values.
- >>> f = vFrequency('bad test')
- Traceback (most recent call last):
- ...
- ValueError: Expected frequency, got: BAD TEST
- >>> vFrequency('daily').ical()
- 'DAILY'
- >>> vFrequency('daily').from_ical('MONTHLY')
- 'MONTHLY'
- """
-
- frequencies = CaselessDict({
- "SECONDLY":"SECONDLY",
- "MINUTELY":"MINUTELY",
- "HOURLY":"HOURLY",
- "DAILY":"DAILY",
- "WEEKLY":"WEEKLY",
- "MONTHLY":"MONTHLY",
- "YEARLY":"YEARLY",
- })
-
- def __init__(self, *args, **kwargs):
- str.__init__(self, *args, **kwargs)
- if not self in vFrequency.frequencies:
- raise ValueError, 'Expected frequency, got: %s' % self
- self.params = Parameters()
-
- def ical(self):
- return self.upper()
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return vFrequency(ical.upper())
- except:
- raise ValueError, 'Expected weekday abbrevation, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vRecur(CaselessDict):
- """
- Let's see how close we can get to one from the rfc:
- FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30
-
- >>> r = dict(freq='yearly', interval=2)
- >>> r['bymonth'] = 1
- >>> r['byday'] = 'su'
- >>> r['byhour'] = [8,9]
- >>> r['byminute'] = 30
- >>> r = vRecur(r)
- >>> r.ical()
- 'BYHOUR=8,9;BYDAY=SU;BYMINUTE=30;BYMONTH=1;FREQ=YEARLY;INTERVAL=2'
-
- >>> r = vRecur(FREQ='yearly', INTERVAL=2)
- >>> r['BYMONTH'] = 1
- >>> r['BYDAY'] = 'su'
- >>> r['BYHOUR'] = [8,9]
- >>> r['BYMINUTE'] = 30
- >>> r.ical()
- 'BYDAY=SU;BYMINUTE=30;BYMONTH=1;INTERVAL=2;FREQ=YEARLY;BYHOUR=8,9'
-
- >>> r = vRecur(freq='DAILY', count=10)
- >>> r['bysecond'] = [0, 15, 30, 45]
- >>> r.ical()
- 'COUNT=10;FREQ=DAILY;BYSECOND=0,15,30,45'
-
- >>> r = vRecur(freq='DAILY', until=datetime(2005,1,1,12,0,0))
- >>> r.ical()
- 'FREQ=DAILY;UNTIL=20050101T120000'
-
- How do we fare with regards to parsing?
- >>> r = vRecur.from_ical('FREQ=DAILY;INTERVAL=2;COUNT=10')
- >>> r
- {'COUNT': [10], 'FREQ': ['DAILY'], 'INTERVAL': [2]}
- >>> vRecur(r).ical()
- 'COUNT=10;FREQ=DAILY;INTERVAL=2'
-
- >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=-SU;BYHOUR=8,9;BYMINUTE=30')
- >>> r
- {'BYHOUR': [8, 9], 'BYDAY': ['-SU'], 'BYMINUTE': [30], 'BYMONTH': [1], 'FREQ': ['YEARLY'], 'INTERVAL': [2]}
- >>> vRecur(r).ical()
- 'BYDAY=-SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9'
-
- Some examples from the spec
-
- >>> r = vRecur.from_ical('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1')
- >>> vRecur(r).ical()
- 'BYSETPOS=-1;FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR'
-
- >>> r = vRecur.from_ical('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30')
- >>> vRecur(r).ical()
- 'BYDAY=SU;BYMINUTE=30;INTERVAL=2;BYMONTH=1;FREQ=YEARLY;BYHOUR=8,9'
-
- and some errors
- >>> r = vRecur.from_ical('BYDAY=12')
- Traceback (most recent call last):
- ...
- ValueError: Error in recurrence rule: BYDAY=12
-
- """
-
- frequencies = ["SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY",
- "MONTHLY", "YEARLY"]
-
- types = CaselessDict({
- 'COUNT':vInt,
- 'INTERVAL':vInt,
- 'BYSECOND':vInt,
- 'BYMINUTE':vInt,
- 'BYHOUR':vInt,
- 'BYMONTHDAY':vInt,
- 'BYYEARDAY':vInt,
- 'BYMONTH':vInt,
- 'UNTIL':vDDDTypes,
- 'BYSETPOS':vInt,
- 'WKST':vWeekday,
- 'BYDAY':vWeekday,
- 'FREQ':vFrequency
- })
-
- def __init__(self, *args, **kwargs):
- CaselessDict.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def ical(self):
- # SequenceTypes
- result = []
- for key, vals in self.items():
- typ = self.types[key]
- if not type(vals) in SequenceTypes:
- vals = [vals]
- vals = ','.join([typ(val).ical() for val in vals])
- result.append('%s=%s' % (key, vals))
- return ';'.join(result)
-
- def parse_type(key, values):
- # integers
- parser = vRecur.types.get(key, vText)
- return [parser.from_ical(v) for v in values.split(',')]
- parse_type = staticmethod(parse_type)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- recur = vRecur()
- for pairs in ical.split(';'):
- key, vals = pairs.split('=')
- recur[key] = vRecur.parse_type(key, vals)
- return dict(recur)
- except:
- raise ValueError, 'Error in recurrence rule: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vText(unicode):
- """
- Simple text
- >>> t = vText(u'Simple text')
- >>> t.ical()
- 'Simple text'
-
- Escaped text
- >>> t = vText('Text ; with escaped, chars')
- >>> t.ical()
- 'Text \\\\; with escaped\\\\, chars'
-
- Escaped newlines
- >>> vText('Text with escaped\N chars').ical()
- 'Text with escaped\\\\n chars'
-
- If you pass a unicode object, it will be utf-8 encoded. As this is the
- (only) standard that RFC 2445 support.
-
- >>> t = vText(u'international chars æøå ÆØÅ ü')
- >>> t.ical()
- 'international chars \\xc3\\xa6\\xc3\\xb8\\xc3\\xa5 \\xc3\\x86\\xc3\\x98\\xc3\\x85 \\xc3\\xbc'
-
- Unicode is converted to utf-8
- >>> t = vText(u'international æ ø å')
- >>> str(t)
- 'international \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5'
-
- and parsing?
- >>> vText.from_ical('Text \\; with escaped\\, chars')
- u'Text ; with escaped, chars'
-
- >>> print vText.from_ical('A string with\\; some\\\\ characters in\\Nit')
- A string with; some\\ characters in
- it
- """
-
- encoding = 'utf-8'
-
- def __init__(self, *args, **kwargs):
- unicode.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def escape(self):
- """
- Format value according to iCalendar TEXT escaping rules.
- """
- return (self.replace('\N', '\n')
- .replace('\\', '\\\\')
- .replace(';', r'\;')
- .replace(',', r'\,')
- .replace('\r\n', r'\n')
- .replace('\n', r'\n')
- )
-
- def __repr__(self):
- return u"vText(%s)" % unicode.__repr__(self)
-
- def ical(self):
- return self.escape().encode(self.encoding)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- ical = (ical.replace(r'\N', r'\n')
- .replace(r'\r\n', '\n')
- .replace(r'\n', '\n')
- .replace(r'\,', ',')
- .replace(r'\;', ';')
- .replace('\\\\', '\\'))
- return ical.decode(vText.encoding)
- except:
- raise ValueError, 'Expected ical text, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vTime(time):
- """
- A subclass of datetime, that renders itself in the iCalendar time
- format.
- >>> dt = vTime(12, 30, 0)
- >>> dt.ical()
- '123000'
-
- >>> vTime.from_ical('123000')
- datetime.time(12, 30)
-
- We should also fail, right?
- >>> vTime.from_ical('263000')
- Traceback (most recent call last):
- ...
- ValueError: Expected time, got: 263000
- """
-
- def __init__(self, *args, **kwargs):
- time.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def ical(self):
- return self.strftime("%H%M%S")
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- timetuple = map(int, (ical[:2],ical[2:4],ical[4:6]))
- return time(*timetuple)
- except:
- raise ValueError, 'Expected time, got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vUri(str):
- """
- Uniform resource identifier is basically just an unquoted string.
- >>> u = vUri('http://www.example.com/')
- >>> u.ical()
- 'http://www.example.com/'
- >>> vUri.from_ical('http://www.example.com/') # doh!
- 'http://www.example.com/'
- """
-
- def __init__(self, *args, **kwargs):
- str.__init__(self, *args, **kwargs)
- self.params = Parameters()
-
- def ical(self):
- return str(self)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- return str(ical)
- except:
- raise ValueError, 'Expected , got: %s' % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return str.__str__(self)
-
-
-
-class vGeo:
- """
- A special type that is only indirectly defined in the rfc.
-
- >>> g = vGeo((1.2, 3.0))
- >>> g.ical()
- '1.2;3.0'
-
- >>> g = vGeo.from_ical('37.386013;-122.082932')
- >>> g
- (37.386012999999998, -122.082932)
-
- >>> vGeo(g).ical()
- '37.386013;-122.082932'
-
- >>> vGeo('g').ical()
- Traceback (most recent call last):
- ...
- ValueError: Input must be (float, float) for latitude and longitude
- """
-
- def __init__(self, geo):
- try:
- latitude, longitude = geo
- latitude = float(latitude)
- longitude = float(longitude)
- except:
- raise ValueError('Input must be (float, float) for latitude and longitude')
- self.latitude = latitude
- self.longitude = longitude
- self.params = Parameters()
-
- def ical(self):
- return '%s;%s' % (self.latitude, self.longitude)
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- latitude, longitude = ical.split(';')
- return (float(latitude), float(longitude))
- except:
- raise ValueError, "Expected 'float;float' , got: %s" % ical
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vUTCOffset:
- """
- Renders itself as a utc offset
-
- >>> u = vUTCOffset(timedelta(hours=2))
- >>> u.ical()
- '+0200'
-
- >>> u = vUTCOffset(timedelta(hours=-5))
- >>> u.ical()
- '-0500'
-
- >>> u = vUTCOffset(timedelta())
- >>> u.ical()
- '0000'
-
- >>> u = vUTCOffset(timedelta(minutes=-30))
- >>> u.ical()
- '-0030'
-
- >>> u = vUTCOffset(timedelta(hours=2, minutes=-30))
- >>> u.ical()
- '+0130'
-
- >>> u = vUTCOffset(timedelta(hours=1, minutes=30))
- >>> u.ical()
- '+0130'
-
- Parsing
-
- >>> vUTCOffset.from_ical('0000')
- datetime.timedelta(0)
-
- >>> vUTCOffset.from_ical('-0030')
- datetime.timedelta(-1, 84600)
-
- >>> vUTCOffset.from_ical('+0200')
- datetime.timedelta(0, 7200)
-
- >>> o = vUTCOffset.from_ical('+0230')
- >>> vUTCOffset(o).ical()
- '+0230'
-
- And a few failures
- >>> vUTCOffset.from_ical('+323k')
- Traceback (most recent call last):
- ...
- ValueError: Expected utc offset, got: +323k
-
- >>> vUTCOffset.from_ical('+2400')
- Traceback (most recent call last):
- ...
- ValueError: Offset must be less than 24 hours, was +2400
- """
-
- def __init__(self, td):
- if not isinstance(td, timedelta):
- raise ValueError('Offset value MUST be a timedelta instance')
- self.td = td
- self.params = Parameters()
-
- def ical(self):
- td = self.td
- day_in_minutes = (td.days * 24 * 60)
- seconds_in_minutes = td.seconds // 60
- total_minutes = day_in_minutes + seconds_in_minutes
- if total_minutes == 0:
- sign = '%s'
- elif total_minutes < 0:
- sign = '-%s'
- else:
- sign = '+%s'
- hours = abs(total_minutes) // 60
- minutes = total_minutes % 60
- duration = '%02i%02i' % (hours, minutes)
- return sign % duration
-
- def from_ical(ical):
- "Parses the data format from ical text format"
- try:
- sign, hours, minutes = (ical[-5:-4], int(ical[-4:-2]), int(ical[-2:]))
- offset = timedelta(hours=hours, minutes=minutes)
- except:
- raise ValueError, 'Expected utc offset, got: %s' % ical
- if offset >= timedelta(hours=24):
- raise ValueError, 'Offset must be less than 24 hours, was %s' % ical
- if sign == '-':
- return -offset
- return offset
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return self.ical()
-
-
-
-class vInline(str):
- """
- This is an especially dumb class that just holds raw unparsed text and has
- parameters. Conversion of inline values are handled by the Component class,
- so no further processing is needed.
-
- >>> vInline('Some text')
- 'Some text'
-
- >>> vInline.from_ical('Some text')
- 'Some text'
-
- >>> t2 = vInline('other text')
- >>> t2.params['cn'] = 'Test Osterone'
- >>> t2.params
- Parameters({'CN': 'Test Osterone'})
-
- """
-
- def __init__(self,obj):
- self.obj = obj
- self.params = Parameters()
-
- def ical(self):
- return str(self)
-
- def from_ical(ical):
- return str(ical)
- from_ical = staticmethod(from_ical)
-
- def __str__(self):
- return str(self.obj)
-
-
-class TypesFactory(CaselessDict):
- """
- All Value types defined in rfc 2445 are registered in this factory class. To
- get a type you can use it like this.
- >>> factory = TypesFactory()
- >>> datetime_parser = factory['date-time']
- >>> dt = datetime_parser(datetime(2001, 1, 1))
- >>> dt.ical()
- '20010101T000000'
-
- A typical use is when the parser tries to find a content type and use text
- as the default
- >>> value = '20050101T123000'
- >>> value_type = 'date-time'
- >>> typ = factory.get(value_type, 'text')
- >>> typ.from_ical(value)
- datetime.datetime(2005, 1, 1, 12, 30)
-
- It can also be used to directly encode property and parameter values
- >>> comment = factory.ical('comment', u'by Rasmussen, Max Møller')
- >>> str(comment)
- 'by Rasmussen\\\\, Max M\\xc3\\xb8ller'
- >>> factory.ical('priority', 1)
- '1'
- >>> factory.ical('cn', u'Rasmussen, Max Møller')
- 'Rasmussen\\\\, Max M\\xc3\\xb8ller'
-
- >>> factory.from_ical('cn', 'Rasmussen\\\\, Max M\\xc3\\xb8ller')
- u'Rasmussen, Max M\\xf8ller'
-
- The value and parameter names don't overlap. So one factory is enough for
- both kinds.
- """
-
- def __init__(self, *args, **kwargs):
- "Set keys to upper for initial dict"
- CaselessDict.__init__(self, *args, **kwargs)
- self['binary'] = vBinary
- self['boolean'] = vBoolean
- self['cal-address'] = vCalAddress
- self['date'] = vDDDTypes
- self['date-time'] = vDDDTypes
- self['duration'] = vDDDTypes
- self['float'] = vFloat
- self['integer'] = vInt
- self['period'] = vPeriod
- self['recur'] = vRecur
- self['text'] = vText
- self['time'] = vTime
- self['uri'] = vUri
- self['utc-offset'] = vUTCOffset
- self['geo'] = vGeo
- self['inline'] = vInline
- self['date-time-list'] = vDDDLists
-
-
- #################################################
- # Property types
-
- # These are the default types
- types_map = CaselessDict({
- ####################################
- # Property valye types
- # Calendar Properties
- 'calscale' : 'text',
- 'method' : 'text',
- 'prodid' : 'text',
- 'version' : 'text',
- # Descriptive Component Properties
- 'attach' : 'uri',
- 'categories' : 'text',
- 'class' : 'text',
- 'comment' : 'text',
- 'description' : 'text',
- 'geo' : 'geo',
- 'location' : 'text',
- 'percent-complete' : 'integer',
- 'priority' : 'integer',
- 'resources' : 'text',
- 'status' : 'text',
- 'summary' : 'text',
- # Date and Time Component Properties
- 'completed' : 'date-time',
- 'dtend' : 'date-time',
- 'due' : 'date-time',
- 'dtstart' : 'date-time',
- 'duration' : 'duration',
- 'freebusy' : 'period',
- 'transp' : 'text',
- # Time Zone Component Properties
- 'tzid' : 'text',
- 'tzname' : 'text',
- 'tzoffsetfrom' : 'utc-offset',
- 'tzoffsetto' : 'utc-offset',
- 'tzurl' : 'uri',
- # Relationship Component Properties
- 'attendee' : 'cal-address',
- 'contact' : 'text',
- 'organizer' : 'cal-address',
- 'recurrence-id' : 'date-time',
- 'related-to' : 'text',
- 'url' : 'uri',
- 'uid' : 'text',
- # Recurrence Component Properties
- 'exdate' : 'date-time-list',
- 'exrule' : 'recur',
- 'rdate' : 'date-time-list',
- 'rrule' : 'recur',
- # Alarm Component Properties
- 'action' : 'text',
- 'repeat' : 'integer',
- 'trigger' : 'duration',
- # Change Management Component Properties
- 'created' : 'date-time',
- 'dtstamp' : 'date-time',
- 'last-modified' : 'date-time',
- 'sequence' : 'integer',
- # Miscellaneous Component Properties
- 'request-status' : 'text',
- ####################################
- # parameter types (luckilly there is no name overlap)
- 'altrep' : 'uri',
- 'cn' : 'text',
- 'cutype' : 'text',
- 'delegated-from' : 'cal-address',
- 'delegated-to' : 'cal-address',
- 'dir' : 'uri',
- 'encoding' : 'text',
- 'fmttype' : 'text',
- 'fbtype' : 'text',
- 'language' : 'text',
- 'member' : 'cal-address',
- 'partstat' : 'text',
- 'range' : 'text',
- 'related' : 'text',
- 'reltype' : 'text',
- 'role' : 'text',
- 'rsvp' : 'boolean',
- 'sent-by' : 'cal-address',
- 'tzid' : 'text',
- 'value' : 'text',
- })
-
-
- def for_property(self, name):
- "Returns a the default type for a property or parameter"
- return self[self.types_map.get(name, 'text')]
-
- def ical(self, name, value):
- """
- Encodes a named value from a primitive python type to an
- icalendar encoded string.
- """
- type_class = self.for_property(name)
- return type_class(value).ical()
-
- def from_ical(self, name, value):
- """
- Decodes a named property or parameter value from an icalendar encoded
- string to a primitive python type.
- """
- type_class = self.for_property(name)
- decoded = type_class.from_ical(str(value))
- return decoded
diff --git a/Webcal/icalendar/tests/__init__.py b/Webcal/icalendar/tests/__init__.py
deleted file mode 100644
index a003759..0000000
--- a/Webcal/icalendar/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# this is a package
diff --git a/Webcal/icalendar/tests/test_icalendar.py b/Webcal/icalendar/tests/test_icalendar.py
deleted file mode 100644
index a96073a..0000000
--- a/Webcal/icalendar/tests/test_icalendar.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- Encoding: utf-8 -*-
-import unittest, doctest, os
-from icalendar import cal, caselessdict, parser, prop
-
-def test_suite():
- suite = unittest.TestSuite()
-
- suite.addTest(doctest.DocTestSuite(caselessdict))
- suite.addTest(doctest.DocTestSuite(parser))
- suite.addTest(doctest.DocTestSuite(prop))
- suite.addTest(doctest.DocTestSuite(cal))
- doc_dir = '../../../doc'
- for docfile in ['example.txt', 'groupscheduled.txt',
- 'small.txt', 'multiple.txt', 'recurrence.txt']:
- suite.addTest(doctest.DocFileSuite(os.path.join(doc_dir, docfile),
- optionflags=doctest.ELLIPSIS),)
- return suite
diff --git a/Webcal/icalendar/tools.py b/Webcal/icalendar/tools.py
deleted file mode 100644
index b6cb746..0000000
--- a/Webcal/icalendar/tools.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- Encoding: utf-8 -*-
-from string import ascii_letters, digits
-import random
-
-"""
-This module contains non-essential tools for iCalendar. Pretty thin so far eh?
-
-"""
-
-class UIDGenerator:
-
- """
- If you are too lazy to create real uid's. Notice, this doctest is disabled!
-
- Automatic semi-random uid
- >> g = UIDGenerator()
- >> uid = g.uid()
- >> uid.ical()
- '20050109T153222-7ekDDHKcw46QlwZK@example.com'
-
- You Should at least insert your own hostname to be more complient
- >> g = UIDGenerator()
- >> uid = g.uid('Example.ORG')
- >> uid.ical()
- '20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG'
-
- You can also insert a path or similar
- >> g = UIDGenerator()
- >> uid = g.uid('Example.ORG', '/path/to/content')
- >> uid.ical()
- '20050109T153415-/path/to/content@Example.ORG'
- """
-
- chars = list(ascii_letters + digits)
-
- def rnd_string(self, length=16):
- "Generates a string with random characters of length"
- return ''.join([random.choice(self.chars) for i in range(length)])
-
- def uid(self, host_name='example.com', unique=''):
- """
- Generates a unique id consisting of:
- datetime-uniquevalue@host. Like:
- 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com
- """
- from PropertyValues import vText, vDatetime
- unique = unique or self.rnd_string()
- return vText('%s-%s@%s' % (vDatetime.today().ical(), unique, host_name))
-
-
-if __name__ == "__main__":
- import os.path, doctest, tools
- # import and test this file
- doctest.testmod(tools)
diff --git a/Webcal/icalendar/util.py b/Webcal/icalendar/util.py
deleted file mode 100644
index eadc3ef..0000000
--- a/Webcal/icalendar/util.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- Encoding: utf-8 -*-
-from string import ascii_letters, digits
-import random
-
-"""
-This module contains non-essential tools for iCalendar. Pretty thin so far eh?
-
-"""
-
-class UIDGenerator:
-
- """
- If you are too lazy to create real uids.
-
- NOTE: this doctest is disabled
- (only two > instead of three)
-
- Automatic semi-random uid
- >> g = UIDGenerator()
- >> uid = g.uid()
- >> uid.ical()
- '20050109T153222-7ekDDHKcw46QlwZK@example.com'
-
- You should at least insert your own hostname to be more compliant
- >> g = UIDGenerator()
- >> uid = g.uid('Example.ORG')
- >> uid.ical()
- '20050109T153549-NbUItOPDjQj8Ux6q@Example.ORG'
-
- You can also insert a path or similar
- >> g = UIDGenerator()
- >> uid = g.uid('Example.ORG', '/path/to/content')
- >> uid.ical()
- '20050109T153415-/path/to/content@Example.ORG'
- """
-
- chars = list(ascii_letters + digits)
-
- def rnd_string(self, length=16):
- "Generates a string with random characters of length"
- return ''.join([random.choice(self.chars) for i in range(length)])
-
- def uid(self, host_name='example.com', unique=''):
- """
- Generates a unique id consisting of:
- datetime-uniquevalue@host. Like:
- 20050105T225746Z-HKtJMqUgdO0jDUwm@example.com
- """
- from PropertyValues import vText, vDatetime
- unique = unique or self.rnd_string()
- return vText('%s-%s@%s' % (vDatetime.today().ical(), unique, host_name))
diff --git a/Webcal/plugin.py b/Webcal/plugin.py
deleted file mode 100644
index f19513e..0000000
--- a/Webcal/plugin.py
+++ /dev/null
@@ -1,324 +0,0 @@
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2005-2007 Dennis Kaarsemaker
-# Copyright (c) 2008-2010 Terence Simpson
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-import supybot.utils as utils
-from supybot.commands import *
-import supybot.plugins as plugins
-import supybot.ircutils as ircutils
-import supybot.callbacks as callbacks
-import supybot.schedule as schedule
-import supybot.ircmsgs as ircmsgs
-import supybot.ircdb as ircdb
-import supybot.conf as conf
-
-def checkIgnored(hostmask, recipient='', users=ircdb.users, channels=ircdb.channels):
- if ircdb.ignores.checkIgnored(hostmask):
- return True
- try:
- id = ircdb.users.getUserId(hostmask)
- user = users.getUser(id)
- except KeyError:
- # If there's no user...
- if ircutils.isChannel(recipient):
- channel = channels.getChannel(recipient)
- if channel.checkIgnored(hostmask):
- return True
- else:
- return False
- else:
- return False
- if user._checkCapability('owner'):
- # Owners shouldn't ever be ignored.
- return False
- elif user.ignore:
- return True
- elif recipient:
- if ircutils.isChannel(recipient):
- channel = ircdb.channels.getChannel(recipient)
- if channel.checkIgnored(hostmask):
- return True
- else:
- return False
- else:
- return False
- else:
- return False
-
-import pytz
-import ical
-reload(ical)
-import datetime, shelve, re
-import cPickle as pickle
-
-class Webcal(callbacks.Plugin):
- """@schedule
- display the schedule in your timezone
- """
- threaded = True
- noIgnore = True
-
- def __init__(self, irc):
- parent = super(Webcal, self)
- parent.__init__(irc)
- self.irc = irc
- self.cache = {}
- self.firstevent = {}
- try:
- schedule.removeEvent(self.name())
- schedule.removeEvent(self.name() + 'b')
- except AssertionError:
- pass
- except KeyError:
- pass
- try:
- schedule.addPeriodicEvent(self.refresh_cache, 60 * 20, name=self.name())
- schedule.addPeriodicEvent(self.autotopics, 60, name=self.name() + 'b')
- except AssertionError:
- pass
-
- def die(self):
- try:
- schedule.removeEvent(self.name())
- schedule.removeEvent(self.name() + 'b')
- except AssertionError:
- pass
- self.cache.clear()
-
- def reset(self):
- self.cache.clear()
-
- def update(self, url):
- data = utils.web.getUrl(url)
- parser = ical.ICalReader(data)
- self.cache[url] = parser.events
-
- def refresh_cache(self):
- for c in self.irc.state.channels:
- url = self.registryValue('url', c)
- if url:
- self.update(url)
-
- def autotopics(self):
- for c in self.irc.state.channels:
- url = self.registryValue('url', c)
- if url and self.registryValue('doTopic', c):
- if url not in self.cache:
- self.update(url)
- events = self.filter(self.cache[url], c)
- #if events[0].is_on() and self.firstevent[c].summary == events[0].summary:
- # continue
- newtopic = self.maketopic(c, template=self.registryValue('topic',c))
- if newtopic.strip() != self.irc.state.getTopic(c).strip():
- self.irc.queueMsg(ircmsgs.topic(c, newtopic))
-
- def filter(self, events, channel):
- now = datetime.datetime.now(pytz.UTC)
- fword = self.registryValue('filter', channel)
- ret = [x for x in events if fword.lower() in x.raw_data.lower() and x.seconds_ago() < 1800]
- ret = [x for x in ret if self.filterChannel(x)]
- ret.sort()
- ret.sort()
- return ret
-
- def filterChannel(self, event):
- desc = event['description']
- where = u'#ubuntu-meeting'
- if "Location\\:" in desc:
- where = desc.split('<')[1].split()[-1]
- if where[0] != u'#':
- where = u'#ubuntu-meeting'
- return where == u'#ubuntu-meeting'
-
- def maketopic(self, c, tz=None, template='%s', num_events=6):
- url = self.registryValue('url',c)
- if not tz:
- tz = 'UTC'
- if url not in self.cache.keys():
- self.update(url)
-
- now = datetime.datetime.now(pytz.UTC)
- events = self.filter(self.cache[url],c)[:num_events]
-# events.sort()
- preamble = ''
- if not len(events):
- return template % "No meetings scheduled"
- # The standard slack of 30 minutes after the meeting will be an
- # error if there are 2 conscutive meetings, so remove the first
- # one in that case
- if len(events) > 1 and events[1].startTime() < now:
- events = events[1:]
- ev0 = events[0]
- if ev0.seconds_to_go() < 600:
- preamble = 'Current meeting: %s ' % ev0.summary.replace('Meeting','').strip()
- if num_events == 1:
- return preamble + (template % '')
- events = events[1:]
-
- if num_events == 1:
- ev = events[0]
- return template % ('Next meeting: %s in %s' % (ev.summary.replace(' Meeting','').strip(), ev.time_to_go()))
-
- events = [x.schedule(tz) for x in events]
- return preamble + (template % ' | '.join(events))
-
- # Now the commands
- def topic(self, irc, msg, args):
- """No args
- Updates the topics in the channel
- """
- c = msg.args[0]
- url = self.registryValue('url', c)
- if not url or not self.registryValue('doTopic',channel=c):
- return
- self.update(url)
-
- events = self.filter(self.cache[url], c)
- if events and events[0].is_on():
- irc.error("Won't update topic while a meeting is in progress")
- return
-
- newtopic = self.maketopic(c, template=self.registryValue('topic',c))
- if not (newtopic.strip() == irc.state.getTopic(c).strip()):
- irc.queueMsg(ircmsgs.topic(c, newtopic))
- topic = wrap(topic, [('checkCapability', 'admin')])
-
- def _tzfilter(self, tz, ud):
- if tz == ud:
- return True
- pos = tz.find('/')
- while not (pos == -1):
- if tz[pos+1:] == ud:
- return True
- pos = tz.find('/',pos+1)
- # Repeat, with spaces replaced by underscores
- ud = ud.replace(' ','_')
- if tz == ud:
- return True
- pos = tz.find('/')
- while not (pos == -1):
- if tz[pos+1:] == ud:
- return True
- pos = tz.find('/',pos+1)
-
- return False
-
- def schedule(self, irc, msg, args, tz):
- """[]
- Retrieve the date/time of scheduled meetings in a specific timezone, defaults to UTC
- """
- if not tz:
- tz = 'utc'
- if irc.isChannel(msg.args[0]):
- c = msg.args[0]
- else:
- c = self.registryValue('defaultChannel')
- if not c:
- return
- url = self.registryValue('url', c)
- if not url:
- c = self.registryValue('defaultChannel')
- url = self.registryValue('url', c)
- if not url:
- return
- tzs = filter(lambda x: self._tzfilter(x.lower(),tz.lower()), pytz.all_timezones)
- if not tzs:
- irc.error('Unknown timezone: %s - Full list: %s' % (tz, self.registryValue('tzUrl') or 'Value not set'))
- return
- newtopic = self.maketopic(c,tz=tzs[0])
- events = self.filter(self.cache[url], msg.args[0])
- if events and events[0].is_on(): # FIXME channel filter
- irc.error('Please don\'t use @schedule during a meeting')
- irc.reply('Schedule for %s: %s' % (tzs[0], newtopic), private=True)
- else:
- irc.reply('Schedule for %s: %s' % (tzs[0], newtopic))
- schedule = wrap(schedule, [additional('text')])
-
- def now(self, irc, msg, args, tz):
- """[]
- Display the current time, defaults to UTC
- """
- if not tz:
- tz = 'utc'
- if irc.isChannel(msg.args[0]):
- c = msg.args[0]
- else:
- c = self.registryValue('defaultChannel')
- if not c:
- return
- url = self.registryValue('url', c)
- if not url:
- c = self.registryValue('defaultChannel')
- url = self.registryValue('url', c)
- if not url:
- return
- tzs = filter(lambda x: self._tzfilter(x.lower(),tz.lower()), pytz.all_timezones)
- if not tzs:
- irc.error('Unknown timezone: %s - Full list: %s' % (tz, self.registryValue('tzUrl') or 'Value not set'))
- return
- now = datetime.datetime.now(pytz.UTC)
- newtopic = self.maketopic(c,tz=tzs[0],num_events=1)
- events = self.filter(self.cache[url], msg.args[0])
- newtopic = 'Current time in %s: %s - %s' % \
- (tzs[0], datetime.datetime.now(pytz.UTC).astimezone(pytz.timezone(tzs[0])).strftime("%B %d %Y, %H:%M:%S"), newtopic)
-
- if events and events[0].is_on(): # Fixme -- channel filter
- irc.error('Please don\'t use @schedule during a meeting')
- irc.reply(newtopic, private=True)
- else:
- irc.reply(newtopic)
- now = wrap(now, [additional('text')])
- time = now
-
- # Warn people that you manage the topic
- def doTopic(self, irc, msg):
- c = msg.args[0]
- if not self.registryValue('doTopic', c):
- return
- url = self.registryValue('url', c)
- irc.reply("The topic of %s is managed by me and filled with the contents of %s - please don't change manually" %
- (msg.args[0],url), private=True)
-
- def callPrecedence(self, irc):
- before = []
- for cb in irc.callbacks:
- if cb.name() == 'IRCLogin':
- before.append(cb)
- return (before, [])
-
- def inFilter(self, irc, msg):
- if not msg.command == 'PRIVMSG':
- return msg
- if not conf.supybot.defaultIgnore():
- return msg
- s = callbacks.addressed(irc.nick, msg)
- if not s:
- return msg
- if checkIgnored(msg.prefix):
- return msg
- try:
- if ircdb.users.getUser(msg.prefix):
- return msg
- except:
- pass
- cmd, args = (s.split(None, 1) + [None])[:2]
- if cmd and cmd[0] in str(conf.supybot.reply.whenAddressedBy.chars.get(msg.args[0])):
- cmd = cmd[1:]
- if cmd in self.listCommands():
- tokens = callbacks.tokenize(s, channel=msg.args[0])
- self.Proxy(irc, msg, tokens)
- return msg
-
-Class = Webcal
diff --git a/Webcal/rruler.py b/Webcal/rruler.py
deleted file mode 100644
index 75bde7f..0000000
--- a/Webcal/rruler.py
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/usr/bin/env python
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2008-2010 Terence Simpson
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-from dateutil import rrule
-import re, datetime
-
-#wk_days = ('MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU')
-wk_days = re.compile("([0-9]?)([M|T|W|F|S][O|U|E|H|R|A])")
-
-rrule_map = {
-'SECONDLY': rrule.SECONDLY,
-'MINUTELY': rrule.MINUTELY,
-'HOURLY': rrule.HOURLY,
-'DAILY': rrule.DAILY,
-'WEEKLY': rrule.WEEKLY,
-'MONTHLY': rrule.MONTHLY,
-'YEARLY': rrule.YEARLY,
-'MO': rrule.MO,
-'TU': rrule.TU,
-'WE': rrule.WE,
-'TH': rrule.TH,
-'FR': rrule.FR,
-'SA': rrule.SA,
-'SU': rrule.SU }
-
-def rrule_wrapper(*args, **kwargs):
- for k, v in kwargs.iteritems():
- if k == 'byday' or k == 'BYDAY':
- del kwargs[k]
- groups = wk_days.match(v[0]).groups()
- if groups[0]:
- kwargs['byweekday'] = rrule_map[groups[1]](int(groups[0]))
- else:
- kwargs['byweekday'] = rrule_map[groups[1]]
-
- else:
- del kwargs[k]
- k = k.lower()
- if isinstance(v, list):
- if len(v) > 1:
- res = []
- for x in v:
- if isinstance(x, basestring) and wk_days.match(x):
- res.append(rrule_map[wk_days.match(x).group(1)])
- elif v in rrule_map:
- res.append(rrule_map[x])
- elif isinstance(x, datetime.datetime):
- res.append(datetime.datetime.fromordinal(x.toordinal()))
- else:
- res.append(v)
- kwargs[k] = tuple(res)
- else:
- if isinstance(v[0], basestring) and wk_days.match(v[0]):
- kwargs[k] = rrule_map[wk_days.match(v[0]).group(0)]
- elif v[0] in rrule_map:
- kwargs[k] = rrule_map[v[0]]
- elif isinstance(v[0], datetime.datetime):
- kwargs[k] = datetime.datetime.fromordinal(v[0].toordinal())
- else:
- kwargs[k] = v[0]
- else:
- kwargs[k] = v
- return rrule.rrule(*args, **kwargs)
diff --git a/Webcal/test.py b/Webcal/test.py
deleted file mode 100644
index d0a0cfa..0000000
--- a/Webcal/test.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2008-2010 Terence Simpson
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-from supybot.test import *
-class WebcalTestCase(PluginTestCase):
- plugins = ('Webcal',)
diff --git a/Webcal/testical.py b/Webcal/testical.py
deleted file mode 100644
index d6caa5a..0000000
--- a/Webcal/testical.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python
-# -*- Encoding: utf-8 -*-
-###
-# Copyright (c) 2008-2010 Terence Simpson
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of version 2 of the GNU General Public License as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-###
-
-import datetime, pytz, urllib2, ical
-def filter(events):
- ret = [x for x in events if x.seconds_ago() < 1800]
- ret.sort()
- ret.sort() # Needs this twice for some reason
- return ret
-
-data = urllib2.urlopen("http://tinyurl.com/6mzmbr").read()
-parser = ical.ICalReader(data)
-
-events = filter(parser.events)
-
-print "\n".join([x.schedule() for x in events])
diff --git a/Webcal/timezones.html b/Webcal/timezones.html
deleted file mode 100644
index 2263aa0..0000000
--- a/Webcal/timezones.html
+++ /dev/null
@@ -1,592 +0,0 @@
-
-
- Ubotu
-
-
-
-
-
Timezones
-
- These are all timezones known by Uboto. You don't need to use the full
- zone name, the last part is enough. When this is ambiguous, the
- alphabetic first timezone will be used. Timezones are not case sensitive
-
-
ℵ
-
- Examples:
-
- For Europe/Amsterdam:
- @schedule amsterdam
- For America/North_Dakota/Center:
- @schedule North_Dakota/Center
-
-
ℵ
-
All timezones
-
- Africa/Abidjan
- Africa/Accra
- Africa/Addis_Ababa
- Africa/Algiers
- Africa/Asmera
- Africa/Bamako
- Africa/Bangui
- Africa/Banjul
- Africa/Bissau
- Africa/Blantyre
- Africa/Brazzaville
- Africa/Bujumbura
- Africa/Cairo
- Africa/Casablanca
- Africa/Ceuta
- Africa/Conakry
- Africa/Dakar
- Africa/Dar_es_Salaam
- Africa/Djibouti
- Africa/Douala
- Africa/El_Aaiun
- Africa/Freetown
- Africa/Gaborone
- Africa/Harare
- Africa/Johannesburg
- Africa/Kampala
- Africa/Khartoum
- Africa/Kigali
- Africa/Kinshasa
- Africa/Lagos
- Africa/Libreville
- Africa/Lome
- Africa/Luanda
- Africa/Lubumbashi
- Africa/Lusaka
- Africa/Malabo
- Africa/Maputo
- Africa/Maseru
- Africa/Mbabane
- Africa/Mogadishu
- Africa/Monrovia
- Africa/Nairobi
- Africa/Ndjamena
- Africa/Niamey
- Africa/Nouakchott
- Africa/Ouagadougou
- Africa/Porto-Novo
- Africa/Sao_Tome
- Africa/Timbuktu
- Africa/Tripoli
- Africa/Tunis
- Africa/Windhoek
-
-
ℵ
-
- America/Adak
- America/Anchorage
- America/Anguilla
- America/Antigua
- America/Araguaina
- America/Argentina/Buenos_Aires
- America/Argentina/Catamarca
- America/Argentina/ComodRivadavia
- America/Argentina/Cordoba
- America/Argentina/Jujuy
- America/Argentina/La_Rioja
- America/Argentina/Mendoza
- America/Argentina/Rio_Gallegos
- America/Argentina/San_Juan
- America/Argentina/Tucuman
- America/Argentina/Ushuaia
- America/Aruba
- America/Asuncion
- America/Atka
- America/Bahia
- America/Barbados
- America/Belem
- America/Belize
- America/Boa_Vista
- America/Bogota
- America/Boise
- America/Buenos_Aires
- America/Cambridge_Bay
- America/Campo_Grande
- America/Cancun
- America/Caracas
- America/Catamarca
- America/Cayenne
- America/Cayman
- America/Chicago
- America/Chihuahua
- America/Coral_Harbour
- America/Cordoba
- America/Costa_Rica
- America/Cuiaba
- America/Curacao
- America/Danmarkshavn
- America/Dawson
- America/Dawson_Creek
- America/Denver
- America/Detroit
- America/Dominica
- America/Edmonton
- America/Eirunepe
- America/El_Salvador
- America/Ensenada
- America/Fort_Wayne
- America/Fortaleza
- America/Glace_Bay
- America/Godthab
- America/Goose_Bay
- America/Grand_Turk
- America/Grenada
- America/Guadeloupe
- America/Guatemala
- America/Guayaquil
- America/Guyana
- America/Halifax
- America/Havana
- America/Hermosillo
- America/Indiana/Indianapolis
- America/Indiana/Knox
- America/Indiana/Marengo
- America/Indiana/Vevay
- America/Indianapolis
- America/Inuvik
- America/Iqaluit
- America/Jamaica
- America/Jujuy
- America/Juneau
- America/Kentucky/Louisville
- America/Kentucky/Monticello
- America/Knox_IN
- America/La_Paz
- America/Lima
- America/Los_Angeles
- America/Louisville
- America/Maceio
- America/Managua
- America/Manaus
- America/Martinique
- America/Mazatlan
- America/Mendoza
- America/Menominee
- America/Merida
- America/Mexico_City
- America/Miquelon
- America/Monterrey
- America/Montevideo
- America/Montreal
- America/Montserrat
- America/Nassau
- America/New_York
- America/Nipigon
- America/Nome
- America/Noronha
- America/North_Dakota/Center
- America/Panama
- America/Pangnirtung
- America/Paramaribo
- America/Phoenix
- America/Port-au-Prince
- America/Port_of_Spain
- America/Porto_Acre
- America/Porto_Velho
- America/Puerto_Rico
- America/Rainy_River
- America/Rankin_Inlet
- America/Recife
- America/Regina
- America/Rio_Branco
- America/Rosario
- America/Santiago
- America/Santo_Domingo
- America/Sao_Paulo
- America/Scoresbysund
- America/Shiprock
- America/St_Johns
- America/St_Kitts
- America/St_Lucia
- America/St_Thomas
- America/St_Vincent
- America/Swift_Current
- America/Tegucigalpa
- America/Thule
- America/Thunder_Bay
- America/Tijuana
- America/Toronto
- America/Tortola
- America/Vancouver
- America/Virgin
- America/Whitehorse
- America/Winnipeg
- America/Yakutat
- America/Yellowknife
-
-
ℵ
-
- Antarctica/Casey
- Antarctica/Davis
- Antarctica/DumontDUrville
- Antarctica/Mawson
- Antarctica/McMurdo
- Antarctica/Palmer
- Antarctica/Rothera
- Antarctica/South_Pole
- Antarctica/Syowa
- Antarctica/Vostok
-
-
ℵ
-
- Arctic/Longyearbyen
-
-
ℵ
-
- Asia/Aden
- Asia/Almaty
- Asia/Amman
- Asia/Anadyr
- Asia/Aqtau
- Asia/Aqtobe
- Asia/Ashgabat
- Asia/Ashkhabad
- Asia/Baghdad
- Asia/Bahrain
- Asia/Baku
- Asia/Bangkok
- Asia/Beirut
- Asia/Bishkek
- Asia/Brunei
- Asia/Calcutta
- Asia/Choibalsan
- Asia/Chongqing
- Asia/Chungking
- Asia/Colombo
- Asia/Dacca
- Asia/Damascus
- Asia/Dhaka
- Asia/Dili
- Asia/Dubai
- Asia/Dushanbe
- Asia/Gaza
- Asia/Harbin
- Asia/Hong_Kong
- Asia/Hovd
- Asia/Irkutsk
- Asia/Istanbul
- Asia/Jakarta
- Asia/Jayapura
- Asia/Jerusalem
- Asia/Kabul
- Asia/Kamchatka
- Asia/Karachi
- Asia/Kashgar
- Asia/Katmandu
- Asia/Krasnoyarsk
- Asia/Kuala_Lumpur
- Asia/Kuching
- Asia/Kuwait
- Asia/Macao
- Asia/Macau
- Asia/Magadan
- Asia/Makassar
- Asia/Manila
- Asia/Muscat
- Asia/Nicosia
- Asia/Novosibirsk
- Asia/Omsk
- Asia/Oral
- Asia/Phnom_Penh
- Asia/Pontianak
- Asia/Pyongyang
- Asia/Qatar
- Asia/Qyzylorda
- Asia/Rangoon
- Asia/Riyadh
- Asia/Saigon
- Asia/Sakhalin
- Asia/Samarkand
- Asia/Seoul
- Asia/Shanghai
- Asia/Singapore
- Asia/Taipei
- Asia/Tashkent
- Asia/Tbilisi
- Asia/Tehran
- Asia/Tel_Aviv
- Asia/Thimbu
- Asia/Thimphu
- Asia/Tokyo
- Asia/Ujung_Pandang
- Asia/Ulaanbaatar
- Asia/Ulan_Bator
- Asia/Urumqi
- Asia/Vientiane
- Asia/Vladivostok
- Asia/Yakutsk
- Asia/Yekaterinburg
- Asia/Yerevan
-
-
ℵ
-
- Atlantic/Azores
- Atlantic/Bermuda
- Atlantic/Canary
- Atlantic/Cape_Verde
- Atlantic/Faeroe
- Atlantic/Jan_Mayen
- Atlantic/Madeira
- Atlantic/Reykjavik
- Atlantic/South_Georgia
- Atlantic/St_Helena
- Atlantic/Stanley
-
-
ℵ
-
- Australia/ACT
- Australia/Adelaide
- Australia/Brisbane
- Australia/Broken_Hill
- Australia/Canberra
- Australia/Currie
- Australia/Darwin
- Australia/Hobart
- Australia/LHI
- Australia/Lindeman
- Australia/Lord_Howe
- Australia/Melbourne
- Australia/NSW
- Australia/North
- Australia/Perth
- Australia/Queensland
- Australia/South
- Australia/Sydney
- Australia/Tasmania
- Australia/Victoria
- Australia/West
- Australia/Yancowinna
-
-
ℵ
-
- Brazil/Acre
- Brazil/DeNoronha
- Brazil/East
- Brazil/West
-
-
ℵ
-
- CET
- CST6CDT
-
-
ℵ
-
- Canada/Atlantic
- Canada/Central
- Canada/East-Saskatchewan
- Canada/Eastern
- Canada/Mountain
- Canada/Newfoundland
- Canada/Pacific
- Canada/Saskatchewan
- Canada/Yukon
- Chile/Continental
- Chile/EasterIsland
-
-
ℵ
-
- Cuba
- EET
- EST
- EST5EDT
- Egypt
- Eire
-
-
ℵ
-
- Etc/Greenwich
- Etc/UCT
- Etc/UTC
- Etc/Universal
- Etc/Zulu
-
-
ℵ
- Europe/Amsterdam
- Europe/Andorra
- Europe/Athens
- Europe/Belfast
- Europe/Belgrade
- Europe/Berlin
- Europe/Bratislava
- Europe/Brussels
- Europe/Bucharest
- Europe/Budapest
- Europe/Chisinau
- Europe/Copenhagen
- Europe/Dublin
- Europe/Gibraltar
- Europe/Helsinki
- Europe/Istanbul
- Europe/Kaliningrad
- Europe/Kiev
- Europe/Lisbon
- Europe/Ljubljana
- Europe/London
- Europe/Luxembourg
- Europe/Madrid
- Europe/Malta
- Europe/Mariehamn
- Europe/Minsk
- Europe/Monaco
- Europe/Moscow
- Europe/Nicosia
- Europe/Oslo
- Europe/Paris
- Europe/Prague
- Europe/Riga
- Europe/Rome
- Europe/Samara
- Europe/San_Marino
- Europe/Sarajevo
- Europe/Simferopol
- Europe/Skopje
- Europe/Sofia
- Europe/Stockholm
- Europe/Tallinn
- Europe/Tirane
- Europe/Tiraspol
- Europe/Uzhgorod
- Europe/Vaduz
- Europe/Vatican
- Europe/Vienna
- Europe/Vilnius
- Europe/Warsaw
- Europe/Zagreb
- Europe/Zaporozhye
- Europe/Zurich
-
-
ℵ
-
- GB
- GB-Eire
- Greenwich
- HST
- Hongkong
- Iceland
-
-
ℵ
-
- Indian/Antananarivo
- Indian/Chagos
- Indian/Christmas
- Indian/Cocos
- Indian/Comoro
- Indian/Kerguelen
- Indian/Mahe
- Indian/Maldives
- Indian/Mauritius
- Indian/Mayotte
- Indian/Reunion
-
-
ℵ
-
- Iran
- Israel
- Jamaica
- Japan
- Kwajalein
- Libya
- MET
- MST
- MST7MDT
-
-
ℵ
-
- Mexico/BajaNorte
- Mexico/BajaSur
- Mexico/General
-
-
ℵ
-
- NZ
- NZ-CHAT
- Navajo
- PRC
- PST8PDT
-
-
ℵ
-
- Pacific/Apia
- Pacific/Auckland
- Pacific/Chatham
- Pacific/Easter
- Pacific/Efate
- Pacific/Enderbury
- Pacific/Fakaofo
- Pacific/Fiji
- Pacific/Funafuti
- Pacific/Galapagos
- Pacific/Gambier
- Pacific/Guadalcanal
- Pacific/Guam
- Pacific/Honolulu
- Pacific/Johnston
- Pacific/Kiritimati
- Pacific/Kosrae
- Pacific/Kwajalein
- Pacific/Majuro
- Pacific/Marquesas
- Pacific/Midway
- Pacific/Nauru
- Pacific/Niue
- Pacific/Norfolk
- Pacific/Noumea
- Pacific/Pago_Pago
- Pacific/Palau
- Pacific/Pitcairn
- Pacific/Ponape
- Pacific/Port_Moresby
- Pacific/Rarotonga
- Pacific/Saipan
- Pacific/Samoa
- Pacific/Tahiti
- Pacific/Tarawa
- Pacific/Tongatapu
- Pacific/Truk
- Pacific/Wake
- Pacific/Wallis
- Pacific/Yap
-
-
ℵ
-
- Poland
- Portugal
- ROC
- ROK
- Singapore
- Turkey
- UCT
-
-
ℵ
-
- US/Alaska
- US/Aleutian
- US/Arizona
- US/Central
- US/East-Indiana
- US/Eastern
- US/Hawaii
- US/Indiana-Starke
- US/Michigan
- US/Mountain
- US/Pacific
- US/Pacific-New
- US/Samoa
-
-
ℵ
-
- UTC
- Universal
- W-SU
- WET
- Zulu
- posixrules
-
-
-
-
diff --git a/bans.cgi b/bans.cgi
deleted file mode 120000
index ca19de1..0000000
--- a/bans.cgi
+++ /dev/null
@@ -1 +0,0 @@
-Bantracker/cgi/bans.cgi
\ No newline at end of file
diff --git a/bans.tmpl b/bans.tmpl
deleted file mode 120000
index 203d00e..0000000
--- a/bans.tmpl
+++ /dev/null
@@ -1 +0,0 @@
-Bantracker/cgi/bans.tmpl
\ No newline at end of file
diff --git a/empty.tmpl b/empty.tmpl
deleted file mode 120000
index c03fee4..0000000
--- a/empty.tmpl
+++ /dev/null
@@ -1 +0,0 @@
-Bantracker/cgi/empty.tmpl
\ No newline at end of file
diff --git a/timezones.html b/timezones.html
deleted file mode 120000
index b835898..0000000
--- a/timezones.html
+++ /dev/null
@@ -1 +0,0 @@
-Webcal/timezones.html
\ No newline at end of file