diff --git a/radicale/__init__.py b/radicale/__init__.py
index 7711fd85..be8aad92 100644
--- a/radicale/__init__.py
+++ b/radicale/__init__.py
@@ -33,15 +33,19 @@ should have been included in this package.
"""
+import os
import base64
import socket
+# Manage Python2/3 different modules
+# pylint: disable-msg=F0401
try:
from http import client, server
except ImportError:
import httplib as client
import BaseHTTPServer as server
+# pylint: enable-msg=F0401
-from radicale import acl, calendar, config, support, xmlutils
+from radicale import acl, calendar, config, xmlutils
def _check(request, function):
@@ -77,7 +81,9 @@ class HTTPSServer(HTTPServer):
def __init__(self, address, handler):
"""Create server by wrapping HTTP socket in an SSL socket."""
# Fails with Python 2.5, import if needed
+ # pylint: disable-msg=F0401
import ssl
+ # pylint: enable-msg=F0401
HTTPServer.__init__(self, address, handler)
self.socket = ssl.wrap_socket(
@@ -98,14 +104,15 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
check_rights = lambda function: lambda request: _check(request, function)
@property
- def calendar(self):
+ def _calendar(self):
"""The ``calendar.Calendar`` object corresponding to the given path."""
- path = self.path.strip("/").split("/")
- if len(path) >= 2:
- cal = "%s/%s" % (path[0], path[1])
- return calendar.Calendar("radicale", cal)
+ # ``normpath`` should clean malformed and malicious request paths
+ attributes = os.path.normpath(self.path.strip("/")).split("/")
+ if len(attributes) >= 2:
+ path = "%s/%s" % (attributes[0], attributes[1])
+ return calendar.Calendar(path)
- def decode(self, text):
+ def _decode(self, text):
"""Try to decode text according to various parameters."""
# List of charsets to try
charsets = []
@@ -134,7 +141,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
@check_rights
def do_GET(self):
"""Manage GET request."""
- answer = self.calendar.vcalendar.encode(self._encoding)
+ answer = self._calendar.read().encode(self._encoding)
self.send_response(client.OK)
self.send_header("Content-Length", len(answer))
@@ -145,7 +152,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
def do_DELETE(self):
"""Manage DELETE request."""
obj = self.headers.get("If-Match", None)
- answer = xmlutils.delete(obj, self.calendar, self.path)
+ answer = xmlutils.delete(obj, self._calendar, self.path)
self.send_response(client.NO_CONTENT)
self.send_header("Content-Length", len(answer))
@@ -162,7 +169,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
def do_PROPFIND(self):
"""Manage PROPFIND request."""
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
- answer = xmlutils.propfind(xml_request, self.calendar, self.path)
+ answer = xmlutils.propfind(xml_request, self._calendar, self.path)
self.send_response(client.MULTI_STATUS)
self.send_header("DAV", "1, calendar-access")
@@ -173,10 +180,10 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
@check_rights
def do_PUT(self):
"""Manage PUT request."""
- ical_request = self.decode(
+ ical_request = self._decode(
self.rfile.read(int(self.headers["Content-Length"])))
obj = self.headers.get("If-Match", None)
- xmlutils.put(ical_request, self.calendar, self.path, obj)
+ xmlutils.put(ical_request, self._calendar, self.path, obj)
self.send_response(client.CREATED)
@@ -184,9 +191,11 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
def do_REPORT(self):
"""Manage REPORT request."""
xml_request = self.rfile.read(int(self.headers["Content-Length"]))
- answer = xmlutils.report(xml_request, self.calendar, self.path)
+ answer = xmlutils.report(xml_request, self._calendar, self.path)
self.send_response(client.MULTI_STATUS)
self.send_header("Content-Length", len(answer))
self.end_headers()
self.wfile.write(answer)
+
+ # pylint: enable-msg=C0103
diff --git a/radicale/calendar.py b/radicale/calendar.py
index 1db24e5f..e1218516 100644
--- a/radicale/calendar.py
+++ b/radicale/calendar.py
@@ -25,93 +25,209 @@ Define the main classes of a calendar as seen from the server.
"""
-from radicale import support
+import os
+import codecs
+
+from radicale import config
-def hash_tag(vcalendar):
- """Hash an vcalendar string."""
- return str(hash(vcalendar))
+FOLDER = os.path.expanduser(config.get("storage", "folder"))
+
-
-class Calendar(object):
- """Internal calendar class."""
- def __init__(self, user, cal):
- """Initialize the calendar with ``cal`` and ``user`` parameters."""
- # TODO: Use properties from the calendar configuration
- self.support = support.load()
- self.encoding = "utf-8"
- self.owner = "radicale"
- self.user = user
- self.cal = cal
- self.version = "2.0"
- self.ctag = hash_tag(self.vcalendar)
-
- def append(self, vcalendar):
- """Append vcalendar to the calendar."""
- self.ctag = hash_tag(self.vcalendar)
- self.support.append(self.cal, vcalendar)
-
- def remove(self, uid):
- """Remove object named ``uid`` from the calendar."""
- self.ctag = hash_tag(self.vcalendar)
- self.support.remove(self.cal, uid)
-
- def replace(self, uid, vcalendar):
- """Replace objet named ``uid`` by ``vcalendar`` in the calendar."""
- self.ctag = hash_tag(self.vcalendar)
- self.support.remove(self.cal, uid)
- self.support.append(self.cal, vcalendar)
-
- @property
- def vcalendar(self):
- """Unicode calendar from the calendar."""
- return self.support.read(self.cal)
-
- @property
- def etag(self):
- """Etag from calendar."""
- return '"%s"' % hash_tag(self.vcalendar)
-
-
-class Event(object):
- """Internal event class."""
- def __init__(self, vcalendar):
- """Initialize event from ``vcalendar``."""
- self.text = vcalendar
-
- @property
- def etag(self):
- """Etag from event."""
- return '"%s"' % hash_tag(self.text)
+# This function overrides the builtin ``open`` function for this module
+# pylint: disable-msg=W0622
+def open(path, mode="r"):
+ """Open file at ``path`` with ``mode``, automagically managing encoding."""
+ return codecs.open(path, mode, config.get("encoding", "stock"))
+# pylint: enable-msg=W0622
class Header(object):
"""Internal header class."""
- def __init__(self, vcalendar):
- """Initialize header from ``vcalendar``."""
- self.text = vcalendar
+ def __init__(self, text):
+ """Initialize header from ``text``."""
+ self.text = text
-class Timezone(object):
- """Internal timezone class."""
- def __init__(self, vcalendar):
- """Initialize timezone from ``vcalendar``."""
- lines = vcalendar.splitlines()
- for line in lines:
- if line.startswith("TZID:"):
- self.id = line.lstrip("TZID:")
- break
+class Event(object):
+ """Internal event class."""
+ tag = "VEVENT"
- self.text = vcalendar
+ def __init__(self, text):
+ """Initialize event from ``text``."""
+ self.text = text
+
+ @property
+ def etag(self):
+ """Etag from event."""
+ return '"%s"' % hash(self.text)
class Todo(object):
"""Internal todo class."""
- def __init__(self, vcalendar):
- """Initialize todo from ``vcalendar``."""
- self.text = vcalendar
+ # This is not a TODO!
+ # pylint: disable-msg=W0511
+ tag = "VTODO"
+ # pylint: enable-msg=W0511
+
+ def __init__(self, text):
+ """Initialize todo from ``text``."""
+ self.text = text
@property
def etag(self):
"""Etag from todo."""
- return hash_tag(self.text)
+ return '"%s"' % hash(self.text)
+
+
+class Timezone(object):
+ """Internal timezone class."""
+ tag = "VTIMEZONE"
+
+ def __init__(self, text):
+ """Initialize timezone from ``text``."""
+ lines = text.splitlines()
+ for line in lines:
+ if line.startswith("TZID:"):
+ self.name = line.replace("TZID:", "")
+ break
+
+ self.text = text
+
+
+class Calendar(object):
+ """Internal calendar class."""
+ def __init__(self, path):
+ """Initialize the calendar with ``cal`` and ``user`` parameters."""
+ # TODO: Use properties from the calendar configuration
+ self.encoding = "utf-8"
+ self.owner = path.split("/")[0]
+ self.path = os.path.join(FOLDER, path.replace("/", os.path.sep))
+ self.ctag = self.etag
+
+ @staticmethod
+ def _parse(text, obj):
+ """Find ``obj.tag`` items in ``text`` text.
+
+ Return a list of items of type ``obj``.
+
+ """
+ items = []
+
+ lines = text.splitlines()
+ in_item = False
+ item_lines = []
+
+ for line in lines:
+ if line.startswith("BEGIN:%s" % obj.tag):
+ in_item = True
+ item_lines = []
+
+ if in_item:
+ item_lines.append(line)
+ if line.startswith("END:%s" % obj.tag):
+ items.append(obj("\n".join(item_lines)))
+
+ return items
+
+ def append(self, text):
+ """Append ``text`` to calendar."""
+ self.ctag = self.etag
+
+ timezones = self.timezones
+ events = self.events
+ todos = self.todos
+
+ for new_timezone in self._parse(text, Timezone):
+ if new_timezone.name not in [timezone.name
+ for timezone in timezones]:
+ timezones.append(new_timezone)
+
+ for new_event in self._parse(text, Event):
+ if new_event.etag not in [event.etag for event in events]:
+ events.append(new_event)
+
+ for new_todo in self._parse(text, Todo):
+ if new_todo.etag not in [todo.etag for todo in todos]:
+ todos.append(new_todo)
+
+ self.write(timezones=timezones, events=events, todos=todos)
+
+ def remove(self, etag):
+ """Remove object named ``etag`` from the calendar."""
+ self.ctag = self.etag
+ todos = [todo for todo in self.todos if todo.etag != etag]
+ events = [event for event in self.events if event.etag != etag]
+
+ self.write(todos=todos, events=events)
+
+ def replace(self, etag, text):
+ """Replace objet named ``etag`` by ``text`` in the calendar."""
+ self.ctag = self.etag
+ self.remove(etag)
+ self.append(text)
+
+ def write(self, headers=None, timezones=None, events=None, todos=None):
+ """Write calendar with given parameters."""
+ headers = headers or self.headers or (
+ Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
+ Header("VERSION:2.0"))
+ timezones = timezones or self.timezones
+ events = events or self.events
+ todos = todos or self.todos
+
+ # Create folder if absent
+ if not os.path.exists(os.path.dirname(self.path)):
+ os.makedirs(os.path.dirname(self.path))
+
+ text = "\n".join((
+ "BEGIN:VCALENDAR",
+ "\n".join([header.text for header in headers]),
+ "\n".join([timezone.text for timezone in timezones]),
+ "\n".join([todo.text for todo in todos]),
+ "\n".join([event.text for event in events]),
+ "END:VCALENDAR"))
+ return open(self.path, "w").write(text)
+
+ @property
+ def etag(self):
+ """Etag from calendar."""
+ return '"%s"' % hash(self.text)
+
+ @property
+ def text(self):
+ """Calendar as plain text."""
+ try:
+ return open(self.path).read()
+ except IOError:
+ return ""
+
+ @property
+ def headers(self):
+ """Find headers items in calendar."""
+ header_lines = []
+
+ lines = self.text.splitlines()
+ for line in lines:
+ if line.startswith("PRODID:"):
+ header_lines.append(Header(line))
+ for line in lines:
+ if line.startswith("VERSION:"):
+ header_lines.append(Header(line))
+
+ return header_lines
+
+ @property
+ def events(self):
+ """Get list of ``Event`` items in calendar."""
+ return self._parse(self.text, Event)
+
+ @property
+ def todos(self):
+ """Get list of ``Todo`` items in calendar."""
+ return self._parse(self.text, Todo)
+
+ @property
+ def timezones(self):
+ """Get list of ``Timezome`` items in calendar."""
+ return self._parse(self.text, Timezone)
diff --git a/radicale/config.py b/radicale/config.py
index e99b010d..7a95a78b 100644
--- a/radicale/config.py
+++ b/radicale/config.py
@@ -29,10 +29,13 @@ Give a configparser-like interface to read and write configuration.
import os
import sys
+# Manage Python2/3 different modules
+# pylint: disable-msg=F0401
try:
from configparser import RawConfigParser as ConfigParser
except ImportError:
from ConfigParser import RawConfigParser as ConfigParser
+# pylint: enable-msg=F0401
# Default configuration
@@ -43,34 +46,27 @@ INITIAL_CONFIG = {
"daemon": "False",
"ssl": "False",
"certificate": "/etc/apache2/ssl/server.crt",
- "key": "/etc/apache2/ssl/server.key",
- },
+ "key": "/etc/apache2/ssl/server.key"},
"encoding": {
"request": "utf-8",
- "stock": "utf-8",
- },
+ "stock": "utf-8"},
"acl": {
"type": "fake",
"filename": "/etc/radicale/users",
- "encryption": "crypt",
- },
- "support": {
- "type": "plain",
- "folder": os.path.expanduser("~/.config/radicale"),
- "calendar": "radicale/cal",
- },
- }
+ "encryption": "crypt"},
+ "storage": {
+ "folder": os.path.expanduser("~/.config/radicale/calendars")}}
# Create a ConfigParser and configure it
-_CONFIG = ConfigParser()
+_CONFIG_PARSER = ConfigParser()
for section, values in INITIAL_CONFIG.items():
- _CONFIG.add_section(section)
+ _CONFIG_PARSER.add_section(section)
for key, value in values.items():
- _CONFIG.set(section, key, value)
+ _CONFIG_PARSER.set(section, key, value)
-_CONFIG.read("/etc/radicale/config")
-_CONFIG.read(os.path.expanduser("~/.config/radicale/config"))
+_CONFIG_PARSER.read("/etc/radicale/config")
+_CONFIG_PARSER.read(os.path.expanduser("~/.config/radicale/config"))
# Wrap config module into ConfigParser instance
-sys.modules[__name__] = _CONFIG
+sys.modules[__name__] = _CONFIG_PARSER
diff --git a/radicale/ical.py b/radicale/ical.py
deleted file mode 100644
index e10b46d8..00000000
--- a/radicale/ical.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Radicale Server - Calendar Server
-# Copyright © 2008-2010 Guillaume Ayoub
-# Copyright © 2008 Nicolas Kandel
-# Copyright © 2008 Pascal Halter
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This library 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.
-#
-# You should have received a copy of the GNU General Public License
-# along with Radicale. If not, see .
-
-"""
-iCal parsing functions.
-
-"""
-
-# TODO: Manage filters (see xmlutils)
-
-from radicale import calendar
-
-
-def write_calendar(headers=(
- calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
- calendar.Header("VERSION:2.0")),
- timezones=(), todos=(), events=()):
- """Create calendar from given parameters."""
- cal = "\n".join((
- "BEGIN:VCALENDAR",
- "\n".join([header.text for header in headers]),
- "\n".join([timezone.text for timezone in timezones]),
- "\n".join([todo.text for todo in todos]),
- "\n".join([event.text for event in events]),
- "END:VCALENDAR"))
- return "\n".join([line for line in cal.splitlines() if line])
-
-
-def _parse(vcalendar, tag, obj):
- """Find ``tag`` items in ``vcalendar``.
-
- Return a list of items of type ``obj``.
-
- """
- items = []
-
- lines = vcalendar.splitlines()
- in_item = False
- item_lines = []
-
- for line in lines:
- if line.startswith("BEGIN:%s" % tag):
- in_item = True
- item_lines = []
-
- if in_item:
- item_lines.append(line)
- if line.startswith("END:%s" % tag):
- items.append(obj("\n".join(item_lines)))
-
- return items
-
-
-def headers(vcalendar):
- """Find Headers items in ``vcalendar``."""
- header_lines = []
-
- lines = vcalendar.splitlines()
- for line in lines:
- if line.startswith("PRODID:"):
- header_lines.append(calendar.Header(line))
- for line in lines:
- if line.startswith("VERSION:"):
- header_lines.append(calendar.Header(line))
-
- return header_lines
-
-
-def events(vcalendar):
- """Get list of ``Event`` from VEVENTS items in ``vcalendar``."""
- return _parse(vcalendar, "VEVENT", calendar.Event)
-
-
-def todos(vcalendar):
- """Get list of ``Todo`` from VTODO items in ``vcalendar``."""
- return _parse(vcalendar, "VTODO", calendar.Todo)
-
-
-def timezones(vcalendar):
- """Get list of ``Timezome`` from VTIMEZONE items in ``vcalendar``."""
- return _parse(vcalendar, "VTIMEZONE", calendar.Timezone)
diff --git a/radicale/support/__init__.py b/radicale/support/__init__.py
deleted file mode 100644
index ace80ef1..00000000
--- a/radicale/support/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Radicale Server - Calendar Server
-# Copyright © 2008-2010 Guillaume Ayoub
-# Copyright © 2008 Nicolas Kandel
-# Copyright © 2008 Pascal Halter
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This library 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.
-#
-# You should have received a copy of the GNU General Public License
-# along with Radicale. If not, see .
-
-"""
-Calendar storage support configuration.
-
-"""
-
-from radicale import config
-
-def load():
- """Load list of available storage support managers."""
- module = __import__("radicale.support", globals(), locals(),
- [config.get("support", "type")])
- return getattr(module, config.get("support", "type"))
diff --git a/radicale/support/plain.py b/radicale/support/plain.py
deleted file mode 100644
index 3dd58ddd..00000000
--- a/radicale/support/plain.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Radicale Server - Calendar Server
-# Copyright © 2008-2010 Guillaume Ayoub
-# Copyright © 2008 Nicolas Kandel
-# Copyright © 2008 Pascal Halter
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This library 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.
-#
-# You should have received a copy of the GNU General Public License
-# along with Radicale. If not, see .
-
-"""
-Plain text storage.
-
-"""
-
-import os
-import posixpath
-import codecs
-
-from radicale import config, ical
-
-FOLDER = os.path.expanduser(config.get("support", "folder"))
-DEFAULT_CALENDAR = config.get("support", "calendar")
-
-
-def _open(path, mode="r"):
- """Open file at ``path`` with ``mode``, automagically managing encoding."""
- return codecs.open(path, mode, config.get("encoding", "stock"))
-
-
-def calendars():
- """List available calendars paths."""
- available_calendars = []
-
- for filename in os.listdir(FOLDER):
- if os.path.isdir(os.path.join(FOLDER, filename)):
- for cal in os.listdir(os.path.join(FOLDER, filename)):
- available_calendars.append(posixpath.join(filename, cal))
-
- return available_calendars
-
-
-def mkcalendar(name):
- """Write a new calendar called ``name``."""
- user, cal = name.split(posixpath.sep)
- if not os.path.exists(os.path.join(FOLDER, user)):
- os.makedirs(os.path.join(FOLDER, user))
- descriptor = _open(os.path.join(FOLDER, user, cal), "w")
- descriptor.write(ical.write_calendar())
-
-
-def read(cal):
- """Read calendar ``cal``."""
- path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
- return _open(path).read()
-
-
-def append(cal, vcalendar):
- """Append ``vcalendar`` to ``cal``."""
- old_calendar = read(cal)
- old_timezones = [timezone.id for timezone in ical.timezones(old_calendar)]
- path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
-
- old_objects = []
- old_objects.extend([event.etag for event in ical.events(old_calendar)])
- old_objects.extend([todo.etag for todo in ical.todos(old_calendar)])
-
- objects = []
- objects.extend(ical.events(vcalendar))
- objects.extend(ical.todos(vcalendar))
-
- for timezone in ical.timezones(vcalendar):
- if timezone.id not in old_timezones:
- descriptor = _open(path)
- lines = [line for line in descriptor.readlines() if line]
- descriptor.close()
-
- for i, line in enumerate(timezone.text.splitlines()):
- lines.insert(2 + i, line + "\n")
-
- descriptor = _open(path, "w")
- descriptor.writelines(lines)
- descriptor.close()
-
- for obj in objects:
- if obj.etag not in old_objects:
- descriptor = _open(path)
- lines = [line for line in descriptor.readlines() if line]
- descriptor.close()
-
- for line in obj.text.splitlines():
- lines.insert(-1, line + "\n")
-
- descriptor = _open(path, "w")
- descriptor.writelines(lines)
- descriptor.close()
-
-
-def remove(cal, etag):
- """Remove object named ``etag`` from ``cal``."""
- path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
-
- cal = read(cal)
-
- headers = ical.headers(cal)
- timezones = ical.timezones(cal)
- todos = [todo for todo in ical.todos(cal) if todo.etag != etag]
- events = [event for event in ical.events(cal) if event.etag != etag]
-
- descriptor = _open(path, "w")
- descriptor.write(ical.write_calendar(headers, timezones, todos, events))
- descriptor.close()
-
-
-# Create default calendar if not present
-if DEFAULT_CALENDAR:
- if DEFAULT_CALENDAR not in calendars():
- mkcalendar(DEFAULT_CALENDAR)
diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py
index d8866cab..abb7497f 100644
--- a/radicale/xmlutils.py
+++ b/radicale/xmlutils.py
@@ -140,6 +140,7 @@ def propfind(xml_request, calendar, url):
def put(ical_request, calendar, url, obj):
"""Read PUT requests."""
+ # TODO: use url to set hreference
if obj:
# PUT is modifying obj
calendar.replace(obj, ical_request)
@@ -174,11 +175,10 @@ def report(xml_request, calendar, url):
# is that really what is needed?
# Read rfc4791-9.[6|10] for info
for hreference in hreferences:
- headers = ical.headers(calendar.vcalendar)
- timezones = ical.timezones(calendar.vcalendar)
+ headers = ical.headers(calendar.text)
+ timezones = ical.timezones(calendar.text)
- objects = \
- ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar)
+ objects = ical.events(calendar.text) + ical.todos(calendar.text)
if not objects:
# TODO: Read rfc4791-9.[6|10] to find a right answer
diff --git a/setup.py b/setup.py
index 85358d17..0b9d00cf 100755
--- a/setup.py
+++ b/setup.py
@@ -69,7 +69,7 @@ setup(
author_email="guillaume.ayoub@kozea.fr",
url="http://www.radicale.org/",
license="GNU GPL v3",
- packages=["radicale", "radicale.acl", "radicale.support"],
+ packages=["radicale", "radicale.acl"],
scripts=["radicale.py"],
cmdclass={'clean': Clean,
"build_scripts": BuildScripts})