Moved catalogue, chunks, compress, newtagging and pagination applications to apps...
authorMarek Stępniowski <marek@stepniowski.com>
Sun, 7 Sep 2008 20:27:14 +0000 (22:27 +0200)
committerMarek Stępniowski <marek@stepniowski.com>
Sun, 7 Sep 2008 20:27:14 +0000 (22:27 +0200)
114 files changed:
apps/catalogue/__init__.py [new file with mode: 0644]
apps/catalogue/admin.py [new file with mode: 0644]
apps/catalogue/fields.py [new file with mode: 0644]
apps/catalogue/forms.py [new file with mode: 0644]
apps/catalogue/management/__init__.py [new file with mode: 0644]
apps/catalogue/management/commands/__init__.py [new file with mode: 0644]
apps/catalogue/management/commands/importbooks.py [new file with mode: 0644]
apps/catalogue/models.py [new file with mode: 0644]
apps/catalogue/templatetags/__init__.py [new file with mode: 0644]
apps/catalogue/templatetags/catalogue_tags.py [new file with mode: 0644]
apps/catalogue/urls.py [new file with mode: 0644]
apps/catalogue/utils.py [new file with mode: 0644]
apps/catalogue/views.py [new file with mode: 0644]
apps/chunks/__init__.py [new file with mode: 0644]
apps/chunks/admin.py [new file with mode: 0644]
apps/chunks/models.py [new file with mode: 0644]
apps/chunks/templatetags/__init__.py [new file with mode: 0644]
apps/chunks/templatetags/chunks.py [new file with mode: 0644]
apps/compress/__init__.py [new file with mode: 0644]
apps/compress/conf/__init__.py [new file with mode: 0644]
apps/compress/conf/settings.py [new file with mode: 0644]
apps/compress/filter_base.py [new file with mode: 0644]
apps/compress/filters/__init__.py [new file with mode: 0644]
apps/compress/filters/csstidy/__init__.py [new file with mode: 0644]
apps/compress/filters/csstidy_python/__init__.py [new file with mode: 0644]
apps/compress/filters/csstidy_python/csstidy.py [new file with mode: 0644]
apps/compress/filters/csstidy_python/data.py [new file with mode: 0644]
apps/compress/filters/csstidy_python/optimizer.py [new file with mode: 0644]
apps/compress/filters/csstidy_python/output.py [new file with mode: 0644]
apps/compress/filters/csstidy_python/tools.py [new file with mode: 0644]
apps/compress/filters/jsmin/__init__.py [new file with mode: 0644]
apps/compress/filters/jsmin/jsmin.py [new file with mode: 0644]
apps/compress/filters/yui/__init__.py [new file with mode: 0644]
apps/compress/management/__init__.py [new file with mode: 0644]
apps/compress/management/commands/__init__.py [new file with mode: 0644]
apps/compress/management/commands/synccompress.py [new file with mode: 0644]
apps/compress/models.py [new file with mode: 0644]
apps/compress/signals.py [new file with mode: 0644]
apps/compress/templates/compress/css.html [new file with mode: 0644]
apps/compress/templates/compress/css_ie.html [new file with mode: 0644]
apps/compress/templates/compress/js.html [new file with mode: 0644]
apps/compress/templates/compress/js_ie.html [new file with mode: 0644]
apps/compress/templatetags/__init__.py [new file with mode: 0644]
apps/compress/templatetags/compressed.py [new file with mode: 0644]
apps/compress/utils.py [new file with mode: 0644]
apps/newtagging/__init__.py [new file with mode: 0644]
apps/newtagging/admin.py [new file with mode: 0644]
apps/newtagging/managers.py [new file with mode: 0644]
apps/newtagging/models.py [new file with mode: 0644]
apps/newtagging/views.py [new file with mode: 0644]
apps/pagination/__init__.py [new file with mode: 0644]
apps/pagination/middleware.py [new file with mode: 0644]
apps/pagination/models.py [new file with mode: 0644]
apps/pagination/templates/pagination/pagination.html [new file with mode: 0644]
apps/pagination/templatetags/__init__.py [new file with mode: 0644]
apps/pagination/templatetags/pagination_tags.py [new file with mode: 0644]
apps/pagination/tests.py [new file with mode: 0644]
catalogue/__init__.py [deleted file]
catalogue/admin.py [deleted file]
catalogue/fields.py [deleted file]
catalogue/forms.py [deleted file]
catalogue/management/__init__.py [deleted file]
catalogue/management/commands/__init__.py [deleted file]
catalogue/management/commands/importbooks.py [deleted file]
catalogue/models.py [deleted file]
catalogue/templatetags/__init__.py [deleted file]
catalogue/templatetags/catalogue_tags.py [deleted file]
catalogue/urls.py [deleted file]
catalogue/utils.py [deleted file]
catalogue/views.py [deleted file]
chunks/__init__.py [deleted file]
chunks/admin.py [deleted file]
chunks/models.py [deleted file]
chunks/templatetags/__init__.py [deleted file]
chunks/templatetags/chunks.py [deleted file]
compress/__init__.py [deleted file]
compress/conf/__init__.py [deleted file]
compress/conf/settings.py [deleted file]
compress/filter_base.py [deleted file]
compress/filters/__init__.py [deleted file]
compress/filters/csstidy/__init__.py [deleted file]
compress/filters/csstidy_python/__init__.py [deleted file]
compress/filters/csstidy_python/csstidy.py [deleted file]
compress/filters/csstidy_python/data.py [deleted file]
compress/filters/csstidy_python/optimizer.py [deleted file]
compress/filters/csstidy_python/output.py [deleted file]
compress/filters/csstidy_python/tools.py [deleted file]
compress/filters/jsmin/__init__.py [deleted file]
compress/filters/jsmin/jsmin.py [deleted file]
compress/filters/yui/__init__.py [deleted file]
compress/management/__init__.py [deleted file]
compress/management/commands/__init__.py [deleted file]
compress/management/commands/synccompress.py [deleted file]
compress/models.py [deleted file]
compress/signals.py [deleted file]
compress/templates/compress/css.html [deleted file]
compress/templates/compress/css_ie.html [deleted file]
compress/templates/compress/js.html [deleted file]
compress/templates/compress/js_ie.html [deleted file]
compress/templatetags/__init__.py [deleted file]
compress/templatetags/compressed.py [deleted file]
compress/utils.py [deleted file]
newtagging/__init__.py [deleted file]
newtagging/admin.py [deleted file]
newtagging/managers.py [deleted file]
newtagging/models.py [deleted file]
newtagging/views.py [deleted file]
pagination/__init__.py [deleted file]
pagination/middleware.py [deleted file]
pagination/models.py [deleted file]
pagination/templates/pagination/pagination.html [deleted file]
pagination/templatetags/__init__.py [deleted file]
pagination/templatetags/pagination_tags.py [deleted file]
pagination/tests.py [deleted file]

diff --git a/apps/catalogue/__init__.py b/apps/catalogue/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py
new file mode 100644 (file)
index 0000000..2f5d48b
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+from django.contrib import admin
+
+from newtagging.admin import TaggableModelAdmin
+from catalogue.models import Tag, Book, Fragment
+
+
+class TagAdmin(admin.ModelAdmin):
+    list_display = ('name', 'slug', 'sort_key', 'category', 'has_description',)
+    list_filter = ('category',)
+    search_fields = ('name',)
+    ordering = ('name',)
+
+    prepopulated_fields = {'slug': ('name',), 'sort_key': ('name',),}
+    radio_fields = {'category': admin.HORIZONTAL}
+
+
+class BookAdmin(TaggableModelAdmin):
+    tag_model = Tag
+    
+    list_display = ('title', 'slug', 'has_pdf_file', 'has_odt_file', 'has_html_file', 'has_description',)
+    search_fields = ('title',)
+    ordering = ('title',)
+
+    prepopulated_fields = {'slug': ('title',)}
+
+
+class FragmentAdmin(TaggableModelAdmin):
+    tag_model = Tag
+    
+    list_display = ('book', 'anchor',)
+    ordering = ('book', 'anchor',)
+
+
+admin.site.register(Tag, TagAdmin)
+admin.site.register(Book, BookAdmin)
+admin.site.register(Fragment, FragmentAdmin)
+
diff --git a/apps/catalogue/fields.py b/apps/catalogue/fields.py
new file mode 100644 (file)
index 0000000..e1a356e
--- /dev/null
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from django import forms
+from django.forms.widgets import flatatt
+from django.forms.util import smart_unicode
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from django.utils.simplejson import dumps
+
+
+class JQueryAutoCompleteWidget(forms.TextInput):
+    def __init__(self, source, options=None, *args, **kwargs):
+        self.source = source
+        self.options = None
+        if options:
+            self.options = dumps(options)
+        super(JQueryAutoCompleteWidget, self).__init__(*args, **kwargs)
+    
+    def render_js(self, field_id):
+        source = "'%s'" % escape(self.source)
+        options = ''
+        if self.options:
+            options += ', %s' % self.options
+        
+        return u'$(\'#%s\').autocomplete(%s%s);' % (field_id, source, options)
+    
+    def render(self, name, value=None, attrs=None):
+        final_attrs = self.build_attrs(attrs, name=name)
+        if value:
+            final_attrs['value'] = smart_unicode(value)
+        
+        if not self.attrs.has_key('id'):
+            final_attrs['id'] = 'id_%s' % name
+        
+        html = u'''<input type="text" %(attrs)s/>
+            <script type="text/javascript"><!--//
+            %(js)s//--></script>
+            ''' % {
+                'attrs' : flatatt(final_attrs),
+                'js' : self.render_js(final_attrs['id']),
+            }
+        
+        return mark_safe(html)
+
+
+class JQueryAutoCompleteField(forms.CharField):
+    def __init__(self, source, options=None, *args, **kwargs):
+        if 'widget' not in kwargs:
+            kwargs['widget'] = JQueryAutoCompleteWidget(source, options)
+        
+        super(JQueryAutoCompleteField, self).__init__(*args, **kwargs)
+
diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py
new file mode 100644 (file)
index 0000000..279ec71
--- /dev/null
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+from django import forms
+from slughifi import slughifi
+
+from catalogue.models import Tag
+from catalogue.fields import JQueryAutoCompleteField
+
+
+class SearchForm(forms.Form):
+    q = JQueryAutoCompleteField('/katalog/tags/', {'minChars': 2, 'selectFirst': True, 'cacheLength': 50})
+    tags = forms.CharField(widget=forms.HiddenInput, required=False)
+    
+    def __init__(self, *args, **kwargs):
+        tags = kwargs.pop('tags', [])
+        super(SearchForm, self).__init__(*args, **kwargs)
+        self.fields['q'].widget.attrs['title'] = u'tytuł utworu, motyw lub kategoria'
+        self.fields['tags'].initial = '/'.join(tag.slug for tag in Tag.get_tag_list(tags))
+
+
+class UserSetsForm(forms.Form):
+    def __init__(self, book, user, *args, **kwargs):
+        super(UserSetsForm, self).__init__(*args, **kwargs)
+        self.fields['set_ids'] = forms.ChoiceField(
+            choices=[(tag.id, tag.name) for tag in Tag.objects.filter(category='set', user=user)],
+        )
+
+
+class ObjectSetsForm(forms.Form):
+    def __init__(self, obj, user, *args, **kwargs):        
+        super(ObjectSetsForm, self).__init__(*args, **kwargs)
+        self.fields['set_ids'] = forms.MultipleChoiceField(
+            label=u'Półki',
+            required=False,
+            choices=[(tag.id, tag.name) for tag in Tag.objects.filter(category='set', user=user)],
+            initial=[tag.id for tag in obj.tags.filter(category='set', user=user)],
+            widget=forms.CheckboxSelectMultiple
+        )
+        
+
+class NewSetForm(forms.Form):
+    name = forms.CharField(max_length=50, required=True)
+        
+    def save(self, user, commit=True):
+        name = self.cleaned_data['name']
+        new_set = Tag(name=name, slug=slughifi(name), sort_key=slughifi(name),
+            category='set', user=user)
+        
+        new_set.save()
+        return new_set
+
diff --git a/apps/catalogue/management/__init__.py b/apps/catalogue/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/catalogue/management/commands/__init__.py b/apps/catalogue/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/catalogue/management/commands/importbooks.py b/apps/catalogue/management/commands/importbooks.py
new file mode 100644 (file)
index 0000000..22dd237
--- /dev/null
@@ -0,0 +1,48 @@
+import os
+
+from django.core.management.base import BaseCommand
+from django.core.management.color import color_style
+from optparse import make_option
+
+from catalogue.models import Book
+
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--verbosity', action='store', dest='verbosity', default='1',
+            type='choice', choices=['0', '1', '2'],
+            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
+    )
+    help = 'Imports books from the specified directories.'
+    args = 'directory [directory ...]'
+
+    def handle(self, *directories, **options):
+        from django.db import transaction
+
+        self.style = color_style()
+
+        verbosity = int(options.get('verbosity', 1))
+        show_traceback = options.get('traceback', False)
+
+        # Start transaction management.
+        transaction.commit_unless_managed()
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+
+        for dir_name in directories:
+            if not os.path.isdir(dir_name):
+                print self.style.ERROR("Skipping '%s': not a directory." % dir_name)
+            else:
+                for file_name in os.listdir(dir_name):
+                    file_path = os.path.join(dir_name, file_name)
+                    if not os.path.splitext(file_name)[1] == '.xml':
+                        print self.style.NOTICE("Skipping '%s': not an XML file." % file_path)
+                        continue
+                    if verbosity > 0:
+                        print "Parsing '%s'" % file_path
+                    
+                    Book.from_xml_file(file_path)
+        
+        transaction.commit()
+        transaction.leave_transaction_management()
+
diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py
new file mode 100644 (file)
index 0000000..9786150
--- /dev/null
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+from django.db import models
+from django.db.models import permalink, Q
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth.models import User
+from django.core.files import File
+from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
+
+from newtagging.models import TagBase
+from newtagging import managers
+
+from librarian import html, dcparser
+
+
+TAG_CATEGORIES = (
+    ('author', _('author')),
+    ('epoch', _('epoch')),
+    ('kind', _('kind')),
+    ('genre', _('genre')),
+    ('theme', _('theme')),
+    ('set', _('set')),
+)
+
+
+class TagSubcategoryManager(models.Manager):
+    def __init__(self, subcategory):
+        super(TagSubcategoryManager, self).__init__()
+        self.subcategory = subcategory
+        
+    def get_query_set(self):
+        return super(TagSubcategoryManager, self).get_query_set().filter(category=self.subcategory)
+
+
+class Tag(TagBase):
+    name = models.CharField(_('name'), max_length=50, unique=True, db_index=True)
+    slug = models.SlugField(_('slug'), unique=True, db_index=True)
+    sort_key = models.SlugField(_('sort key'), db_index=True)
+    category = models.CharField(_('category'), max_length=50, blank=False, null=False, 
+        db_index=True, choices=TAG_CATEGORIES)
+    description = models.TextField(blank=True)
+    
+    user = models.ForeignKey(User, blank=True, null=True)
+    
+    def has_description(self):
+        return len(self.description) > 0
+    has_description.short_description = _('description')
+    has_description.boolean = True
+
+    @permalink
+    def get_absolute_url(self):
+        return ('catalogue.views.tagged_object_list', [self.slug])
+    
+    class Meta:
+        ordering = ('sort_key',)
+        verbose_name = _('tag')
+        verbose_name_plural = _('tags')
+    
+    def __unicode__(self):
+        return self.name
+
+    @staticmethod
+    def get_tag_list(tags):
+        if isinstance(tags, basestring):
+            tag_slugs = tags.split('/')
+            return [Tag.objects.get(slug=slug) for slug in tag_slugs]
+        else:
+            return TagBase.get_tag_list(tags)
+
+
+class Book(models.Model):
+    title = models.CharField(_('title'), max_length=120)
+    slug = models.SlugField(_('slug'), unique=True, db_index=True)
+    description = models.TextField(_('description'), blank=True)
+    created_at = models.DateTimeField(_('creation date'), auto_now=True)
+    _short_html = models.TextField(_('short HTML'), editable=False)
+    
+    # Formats
+    xml_file = models.FileField(_('XML file'), upload_to='books/xml', blank=True)
+    pdf_file = models.FileField(_('PDF file'), upload_to='books/pdf', blank=True)
+    odt_file = models.FileField(_('ODT file'), upload_to='books/odt', blank=True)
+    html_file = models.FileField(_('HTML file'), upload_to='books/html', blank=True)
+    
+    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
+    
+    objects = models.Manager()
+    tagged = managers.ModelTaggedItemManager(Tag)
+    tags = managers.TagDescriptor(Tag)
+    
+    def short_html(self):
+        if len(self._short_html):
+            return mark_safe(self._short_html)
+        else:
+            tags = self.tags.filter(~Q(category__in=('set', 'theme')))
+            tags = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in tags]
+
+            formats = []
+            if self.html_file:
+                formats.append(u'<a href="%s">Czytaj online</a>' % self.html_file.url)
+            if self.pdf_file:
+                formats.append(u'<a href="%s">Plik PDF</a>' % self.pdf_file.url)
+            if self.odt_file:
+                formats.append(u'<a href="%s">Plik ODT</a>' % self.odt_file.url)
+            
+            self._short_html = unicode(render_to_string('catalogue/book_short.html',
+                {'book': self, 'tags': tags, 'formats': formats}))
+            self.save()
+            return mark_safe(self._short_html)
+    
+    def has_description(self):
+        return len(self.description) > 0
+    has_description.short_description = _('description')
+    has_description.boolean = True
+    
+    def has_pdf_file(self):
+        return bool(self.pdf_file)
+    has_pdf_file.short_description = 'PDF'
+    has_pdf_file.boolean = True
+    
+    def has_odt_file(self):
+        return bool(self.odt_file)
+    has_odt_file.short_description = 'ODT'
+    has_odt_file.boolean = True
+    
+    def has_html_file(self):
+        return bool(self.html_file)
+    has_html_file.short_description = 'HTML'
+    has_html_file.boolean = True
+
+    @staticmethod
+    def from_xml_file(xml_file):
+        from tempfile import NamedTemporaryFile
+        from slughifi import slughifi
+        from markupstring import MarkupString
+        
+        # Read book metadata
+        book_info = dcparser.parse(xml_file)
+        book = Book(title=book_info.title, slug=slughifi(book_info.title))
+        book.save()
+        
+        book_tags = []
+        for category in ('kind', 'genre', 'author', 'epoch'):    
+            tag_name = getattr(book_info, category)
+            tag_sort_key = tag_name
+            if category == 'author':
+                tag_sort_key = tag_name.last_name
+                tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
+            tag, created = Tag.objects.get_or_create(name=tag_name,
+                slug=slughifi(tag_name), sort_key=slughifi(tag_sort_key), category=category)
+            tag.save()
+            book_tags.append(tag)
+        book.tags = book_tags
+        
+        if hasattr(book_info, 'parts'):
+            for part_url in book_info.parts:
+                base, slug = part_url.rsplit('/', 1)
+                child_book = Book.objects.get(slug=slug)
+                child_book.parent = book
+                child_book.save()
+        
+        # Save XML and HTML files
+        book.xml_file.save('%s.xml' % book.slug, File(file(xml_file)), save=False)
+        
+        html_file = NamedTemporaryFile()
+        html.transform(book.xml_file.path, html_file)
+        book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
+        
+        # Extract fragments
+        closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
+        book_themes = []
+        for fragment in closed_fragments.values():
+            text = fragment.to_string()
+            short_text = ''
+            if (len(MarkupString(text)) > 240):
+                short_text = unicode(MarkupString(text)[:160])
+            new_fragment = Fragment(text=text, short_text=short_text, anchor=fragment.id, book=book)
+                
+            theme_names = [s.strip() for s in fragment.themes.split(',')]
+            themes = []
+            for theme_name in theme_names:
+                tag, created = Tag.objects.get_or_create(name=theme_name,
+                    slug=slughifi(theme_name), sort_key=slughifi(theme_name), category='theme')
+                tag.save()
+                themes.append(tag)
+            new_fragment.save()
+            new_fragment.tags = list(book.tags) + themes
+            book_themes += themes
+        
+        book_themes = set(book_themes)
+        book.tags = list(book.tags) + list(book_themes)
+        return book.save()
+    
+    @permalink
+    def get_absolute_url(self):
+        return ('catalogue.views.book_detail', [self.slug])
+        
+    class Meta:
+        ordering = ('title',)
+        verbose_name = _('book')
+        verbose_name_plural = _('books')
+
+    def __unicode__(self):
+        return self.title
+
+
+class Fragment(models.Model):
+    text = models.TextField()
+    short_text = models.TextField(editable=False)
+    _short_html = models.TextField(editable=False)
+    anchor = models.IntegerField()
+    book = models.ForeignKey(Book, related_name='fragments')
+
+    objects = models.Manager()
+    tagged = managers.ModelTaggedItemManager(Tag)
+    tags = managers.TagDescriptor(Tag)
+    
+    def short_html(self):
+        if len(self._short_html):
+            return mark_safe(self._short_html)
+        else:
+            book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) 
+                for tag in self.book.tags if tag.category == 'author']
+            
+            self._short_html = unicode(render_to_string('catalogue/fragment_short.html',
+                {'fragment': self, 'book': self.book, 'book_authors': book_authors}))
+            self.save()
+            return mark_safe(self._short_html)
+        
+    class Meta:
+        ordering = ('book', 'anchor',)
+        verbose_name = _('fragment')
+        verbose_name_plural = _('fragments')
+
diff --git a/apps/catalogue/templatetags/__init__.py b/apps/catalogue/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/catalogue/templatetags/catalogue_tags.py b/apps/catalogue/templatetags/catalogue_tags.py
new file mode 100644 (file)
index 0000000..8cce80d
--- /dev/null
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+from django import template
+from django.template import Node, Variable
+from django.utils.encoding import smart_str
+from django.core.urlresolvers import reverse
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.db.models import Q
+
+
+register = template.Library()
+
+
+class RegistrationForm(UserCreationForm):
+    def as_ul(self):
+        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
+        return self._html_output(u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>', '</li>', u' %s', False)
+
+
+class LoginForm(AuthenticationForm):
+    def as_ul(self):
+        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
+        return self._html_output(u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>', '</li>', u' %s', False)
+
+
+def iterable(obj):
+    try:
+        iter(obj)
+        return True
+    except TypeError:
+        return False
+
+
+def capfirst(text):
+    try:
+        return '%s%s' % (text[0].upper(), text[1:])
+    except IndexError:
+        return ''
+
+
+@register.simple_tag
+def title_from_tags(tags):
+    def split_tags(tags):
+        result = {}
+        for tag in tags:
+            result[tag.category] = tag
+        return result
+    
+    class Flection(object):
+        def get_case(self, name, flection):
+            return name
+    flection = Flection()
+    
+    self = split_tags(tags)
+    
+    title = u''
+    
+    # Specjalny przypadek oglądania wszystkich lektur na danej półce
+    if len(self) == 1 and 'set' in self:
+        return u'Półka %s' % self['set']
+    
+    # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
+    # jest wybrana przez użytkownika
+    if 'epoch' in self and len(self) == 1:
+        text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
+        return capfirst(text)
+    
+    # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
+    # są tylko rodzaj literacki i autor
+    if 'kind' in self and 'author' in self and len(self) == 2:
+        text = u'%s w twórczości %s' % (unicode(self['kind']), 
+            flection.get_case(unicode(self['author']), u'dopełniacz'))
+        return capfirst(text)
+    
+    # Przypadki ogólniejsze
+    if 'theme' in self:
+        title += u'Motyw %s' % unicode(self['theme'])
+    
+    if 'genre' in self:
+        if 'theme' in self:
+            title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
+        else:
+            title += unicode(self['genre'])
+            
+    if 'kind' in self or 'author' in self or 'epoch' in self:
+        if 'genre' in self or 'theme' in self:
+            if 'kind' in self:
+                title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
+            else:
+                title += u' w twórczości '
+        else:
+            title += u'%s ' % unicode(self.get('kind', u'twórczość'))
+            
+    if 'author' in self:
+        title += flection.get_case(unicode(self['author']), u'dopełniacz')
+    elif 'epoch' in self:
+        title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
+    
+    return capfirst(title)
+
+
+@register.simple_tag
+def user_creation_form():
+    return RegistrationForm(prefix='registration').as_ul()
+
+
+@register.simple_tag
+def authentication_form():
+    return LoginForm(prefix='login').as_ul()
+
+
+@register.inclusion_tag('catalogue/breadcrumbs.html')
+def breadcrumbs(tags, search_form=True):
+    from catalogue.forms import SearchForm
+    context = {'tag_list': tags}
+    if search_form:
+        context['search_form'] = SearchForm(tags=tags)
+    return context
+
+
+@register.tag
+def catalogue_url(parser, token):
+    bits = token.split_contents()
+    tag_name = bits[0]
+    
+    tags_to_add = []
+    tags_to_remove = []
+    for bit in bits[1:]:
+        if bit[0] == '-':
+            tags_to_remove.append(bit[1:])
+        else:
+            tags_to_add.append(bit)
+    
+    return CatalogueURLNode(tags_to_add, tags_to_remove)
+
+
+class CatalogueURLNode(Node):
+    def __init__(self, tags_to_add, tags_to_remove):
+        self.tags_to_add = [Variable(tag) for tag in tags_to_add]
+        self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
+    
+    def render(self, context):
+        tags_to_add = []
+        tags_to_remove = []
+
+        for tag_variable in self.tags_to_add:
+            tag = tag_variable.resolve(context)
+            if isinstance(tag, (list, dict)):
+                tags_to_add += [t for t in tag]
+            else:
+                tags_to_add.append(tag)
+
+        for tag_variable in self.tags_to_remove:
+            tag = tag_variable.resolve(context)
+            if iterable(tag):
+                tags_to_remove += [t for t in tag]
+            else:
+                tags_to_remove.append(tag)
+            
+        tag_slugs = [tag.slug for tag in tags_to_add]
+        for tag in tags_to_remove:
+            try:
+                tag_slugs.remove(tag.slug)
+            except KeyError:
+                pass
+        
+        if len(tag_slugs) > 0:
+            return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
+        else:
+            return reverse('main_page')
+
+
+@register.inclusion_tag('catalogue/latest_blog_posts.html')
+def latest_blog_posts(feed_url, posts_to_show=5):
+    import feedparser
+    import datetime
+    
+    feed = feedparser.parse(feed_url)
+    posts = []
+    for i in range(posts_to_show):
+        pub_date = feed['entries'][i].updated_parsed
+        published = datetime.date(pub_date[0], pub_date[1], pub_date[2] )
+        posts.append({
+            'title': feed['entries'][i].title,
+            'summary': feed['entries'][i].summary,
+            'link': feed['entries'][i].link,
+            'date': published,
+            })
+    return {'posts': posts}
+
diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py
new file mode 100644 (file)
index 0000000..f477906
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('catalogue.views',
+    url(r'^$', 'main_page', name='main_page'),
+    url(r'^polki/$', 'user_shelves', name='user_shelves'),
+    url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
+    url(r'^lektury/', 'book_list', name='book_list'),
+    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'),
+    url(r'^fragment/(?P<id>[0-9]+)/polki/', 'fragment_sets', name='fragment_shelves'),
+    url(r'^polki/nowa/$', 'new_set', name='new_set'),
+    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'),
+    url(r'^tags/$', 'tags_starting_with', name='hint'),
+    url(r'^szukaj/$', 'search', name='search'),
+    url(r'^(?P<tags>[a-zA-Z0-9-/]+)/$', 'tagged_object_list', name='tagged_object_list'),
+)
+
diff --git a/apps/catalogue/utils.py b/apps/catalogue/utils.py
new file mode 100644 (file)
index 0000000..d1cee50
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+
+def split_tags(tags):
+    result = {}
+    for tag in tags:
+        result.setdefault(tag.category, []).append(tag)
+    return result
+
diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py
new file mode 100644 (file)
index 0000000..ad9bde7
--- /dev/null
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+from django.template import RequestContext
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.core.urlresolvers import reverse
+from django.db.models import Q
+from django.contrib.auth.decorators import login_required
+from django.utils.datastructures import SortedDict
+from django.views.decorators.http import require_POST
+from django.contrib import auth
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.utils import simplejson
+from django.utils.functional import Promise
+from django.utils.encoding import force_unicode
+
+from catalogue import models
+from catalogue import forms
+from catalogue.utils import split_tags
+from newtagging import views as newtagging_views
+
+
+class LazyEncoder(simplejson.JSONEncoder):
+    def default(self, obj):
+        if isinstance(obj, Promise):
+            return force_unicode(obj)
+        return obj
+
+
+def search(request):
+    query = request.GET.get('q', '')
+    tags = request.GET.get('tags', '')
+    if tags == '':
+        tags = []
+    
+    try:
+        tag_list = models.Tag.get_tag_list(tags)
+        tag = models.Tag.objects.get(name=query)
+    except models.Tag.DoesNotExist:
+        try:
+            book = models.Book.objects.get(title=query)
+            return HttpResponseRedirect(book.get_absolute_url())
+        except models.Book.DoesNotExist:
+            return HttpResponseRedirect(reverse('catalogue.views.main_page'))
+    else:
+        tag_list.append(tag)
+        return HttpResponseRedirect(reverse('catalogue.views.tagged_object_list', 
+            kwargs={'tags': '/'.join(tag.slug for tag in tag_list)}
+        ))
+
+
+def tags_starting_with(request):
+    try:
+        prefix = request.GET['q']
+        if len(prefix) < 2:
+            raise KeyError
+            
+        books = models.Book.objects.filter(title__icontains=prefix)
+        tags = models.Tag.objects.filter(name__icontains=prefix)
+        if request.user.is_authenticated():
+            tags = tags.filter(~Q(category='set') | Q(user=request.user))
+        else:
+            tags = tags.filter(~Q(category='set'))
+        
+        completions = [book.title for book in books] + [tag.name for tag in tags]
+
+        return HttpResponse('\n'.join(completions))    
+    
+    except KeyError:
+        return HttpResponse('')
+
+
+def main_page(request):    
+    if request.user.is_authenticated():
+        extra_where = '(NOT catalogue_tag.category = "set" OR catalogue_tag.user_id = %d)' % request.user.id
+    else:
+        extra_where = 'NOT catalogue_tag.category = "set"'
+    tags = models.Tag.objects.usage_for_model(models.Book, counts=True, extra={'where': [extra_where]})
+    fragment_tags = models.Tag.objects.usage_for_model(models.Fragment, counts=True,
+        extra={'where': ['catalogue_tag.category = "theme"']})
+    categories = split_tags(tags)
+    
+    form = forms.SearchForm()
+    return render_to_response('catalogue/main_page.html', locals(),
+        context_instance=RequestContext(request))
+
+
+def book_list(request):
+    books = models.Book.objects.all()
+    form = forms.SearchForm()
+    
+    books_by_first_letter = SortedDict()
+    for book in books:
+        books_by_first_letter.setdefault(book.title[0], []).append(book)
+    
+    return render_to_response('catalogue/book_list.html', locals(),
+        context_instance=RequestContext(request))
+
+
+def tagged_object_list(request, tags=''):
+    try:
+        tags = models.Tag.get_tag_list(tags)
+    except models.Tag.DoesNotExist:
+        raise Http404
+    
+    model = models.Book
+    theme_is_set = any(tag.category == 'theme' for tag in tags)
+    if theme_is_set:
+        model = models.Fragment
+    
+    if request.user.is_authenticated():
+        extra_where = '(NOT catalogue_tag.category = "set" OR catalogue_tag.user_id = %d)' % request.user.id
+    else:
+        extra_where = 'NOT catalogue_tag.category = "set"'
+    related_tags = models.Tag.objects.related_for_model(tags, model, counts=True, extra={'where': [extra_where]})
+    categories = split_tags(related_tags)
+
+    return newtagging_views.tagged_object_list(
+        request,
+        tag_model=models.Tag,
+        queryset_or_model=model,
+        tags=tags,
+        template_name='catalogue/tagged_object_list.html',
+        extra_context = {'categories': categories },
+    )
+
+
+def book_detail(request, slug):
+    book = get_object_or_404(models.Book, slug=slug)
+    tags = list(book.tags.filter(~Q(category='set')))
+    categories = split_tags(tags)
+    
+    return render_to_response('catalogue/book_detail.html', locals(),
+        context_instance=RequestContext(request))
+
+
+def logout_then_redirect(request):
+    auth.logout(request)
+    return HttpResponseRedirect(request.GET.get('next', '/'))
+
+
+@require_POST
+def register(request):
+    registration_form = UserCreationForm(request.POST, prefix='registration')
+    if registration_form.is_valid():
+        user = registration_form.save()
+        user = auth.authenticate(
+            username=registration_form.cleaned_data['username'], 
+            password=registration_form.cleaned_data['password1']
+        )
+        auth.login(request, user)
+        response_data = {'success': True, 'errors': {}}
+    else:
+        response_data = {'success': False, 'errors': registration_form.errors}
+    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
+
+
+@require_POST
+def login(request):
+    form = AuthenticationForm(data=request.POST, prefix='login')
+    if form.is_valid():
+        auth.login(request, form.get_user())
+        response_data = {'success': True, 'errors': {}}
+    else:
+        response_data = {'success': False, 'errors': form.errors}
+    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
+
+
+def book_sets(request, slug):
+    book = get_object_or_404(models.Book, slug=slug)
+    user_sets = models.Tag.objects.filter(category='set', user=request.user)
+    book_sets = book.tags.filter(category='set', user=request.user)
+    
+    if not request.user.is_authenticated():
+        return HttpResponse('<p>Aby zarządzać swoimi półkami, musisz się zalogować.</p>')
+    
+    if request.method == 'POST':
+        form = forms.ObjectSetsForm(book, request.user, request.POST)
+        if form.is_valid():
+            book.tags = ([models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']] +
+                list(book.tags.filter(~Q(category='set') | ~Q(user=request.user))))
+            if request.is_ajax():
+                return HttpResponse('<p>Półki zostały zapisane.</p>')
+            else:
+                return HttpResponseRedirect('/')
+    else:
+        form = forms.ObjectSetsForm(book, request.user)
+        new_set_form = forms.NewSetForm()
+    
+    return render_to_response('catalogue/book_sets.html', locals(),
+        context_instance=RequestContext(request))
+
+
+def fragment_sets(request, id):
+    fragment = get_object_or_404(models.Fragment, pk=id)
+    user_sets = models.Tag.objects.filter(category='set', user=request.user)
+    fragment_sets = fragment.tags.filter(category='set', user=request.user)
+
+    if not request.user.is_authenticated():
+        return HttpResponse('<p>Aby zarządzać swoimi półkami, musisz się zalogować.</p>')
+
+    if request.method == 'POST':
+        form = forms.ObjectSetsForm(fragment, request.user, request.POST)
+        if form.is_valid():
+            fragment.tags = ([models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']] +
+                list(fragment.tags.filter(~Q(category='set') | ~Q(user=request.user))))
+            if request.is_ajax():
+                return HttpResponse('<p>Półki zostały zapisane.</p>')
+            else:
+                return HttpResponseRedirect('/')
+    else:
+        form = forms.ObjectSetsForm(fragment, request.user)
+        new_set_form = forms.NewSetForm()
+
+    return render_to_response('catalogue/fragment_sets.html', locals(),
+        context_instance=RequestContext(request))
+
+
+@login_required
+@require_POST
+def new_set(request):
+    new_set_form = forms.NewSetForm(request.POST)
+    if new_set_form.is_valid():
+        new_set = new_set_form.save(request.user)
+        return HttpResponse(u'<p>Półka <strong>%s</strong> została utworzona</p>' % new_set)
+    
+    return render_to_response('catalogue/book_sets.html', locals(),
+            context_instance=RequestContext(request))
+
+
+@login_required
+@require_POST
+def delete_shelf(request, slug):
+    user_set = get_object_or_404(models.Tag, slug=slug, category='set', user=request.user)
+    user_set.delete()
+    return HttpResponse(u'<p>Półka <strong>%s</strong> została usunięta</p>' % user_set.name)
+    
+    
+@login_required
+def user_shelves(request):
+    shelves = models.Tag.objects.filter(category='set', user=request.user)
+    new_set_form = forms.NewSetForm()
+    return render_to_response('catalogue/user_shelves.html', locals(),
+            context_instance=RequestContext(request))
+
diff --git a/apps/chunks/__init__.py b/apps/chunks/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/chunks/admin.py b/apps/chunks/admin.py
new file mode 100644 (file)
index 0000000..c44d5a0
--- /dev/null
@@ -0,0 +1,11 @@
+from django.contrib import admin
+
+from chunks.models import Chunk
+
+
+class ChunkAdmin(admin.ModelAdmin):
+    list_display = ('key', 'description',)
+    search_fields = ('key', 'content',)
+
+admin.site.register(Chunk, ChunkAdmin)
+
diff --git a/apps/chunks/models.py b/apps/chunks/models.py
new file mode 100644 (file)
index 0000000..396d221
--- /dev/null
@@ -0,0 +1,21 @@
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+
+class Chunk(models.Model):
+    """
+    A Chunk is a piece of content associated with a unique key that can be inserted into
+    any template with the use of a special template tag.
+    """
+    key = models.CharField(_('key'), help_text=_('A unique name for this chunk of content'), primary_key=True, max_length=255)
+    description = models.CharField(_('description'), blank=True, max_length=255)
+    content = models.TextField(_('content'), blank=True)
+
+    class Meta:
+        ordering = ('key',)
+        verbose_name = _('chunk')
+        verbose_name_plural = _('chunks')
+    
+    def __unicode__(self):
+        return u'%s' % (self.key,)
+
diff --git a/apps/chunks/templatetags/__init__.py b/apps/chunks/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/chunks/templatetags/chunks.py b/apps/chunks/templatetags/chunks.py
new file mode 100644 (file)
index 0000000..f79d495
--- /dev/null
@@ -0,0 +1,45 @@
+from django import template
+from django.db import models
+from django.core.cache import cache
+
+register = template.Library()
+
+Chunk = models.get_model('chunks', 'chunk')
+CACHE_PREFIX = "chunk_"
+
+def do_get_chunk(parser, token):
+    # split_contents() knows not to split quoted strings.
+    tokens = token.split_contents()
+    if len(tokens) < 2 or len(tokens) > 3:
+        raise template.TemplateSyntaxError, "%r tag should have either 2 or 3 arguments" % (tokens[0],)
+    if len(tokens) == 2:
+        tag_name, key = tokens
+        cache_time = 0
+    if len(tokens) == 3:
+        tag_name, key, cache_time = tokens
+    # Check to see if the key is properly double/single quoted
+    if not (key[0] == key[-1] and key[0] in ('"', "'")):
+        raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+    # Send key without quotes and caching time
+    return ChunkNode(key[1:-1], cache_time)
+
+class ChunkNode(template.Node):
+    def __init__(self, key, cache_time=0):
+       self.key = key
+       self.cache_time = cache_time
+    
+    def render(self, context):
+        try:
+            cache_key = CACHE_PREFIX + self.key
+            c = cache.get(cache_key)
+            if c is None:
+                c = Chunk.objects.get(key=self.key)
+                cache.set(cache_key, c, int(self.cache_time))
+            content = c.content
+        except Chunk.DoesNotExist:
+            n = Chunk(key=self.key)
+            n.save()
+            return ''
+        return content
+        
+register.tag('chunk', do_get_chunk)
diff --git a/apps/compress/__init__.py b/apps/compress/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/conf/__init__.py b/apps/compress/conf/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/conf/settings.py b/apps/compress/conf/settings.py
new file mode 100644 (file)
index 0000000..f6949a2
--- /dev/null
@@ -0,0 +1,22 @@
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+
+COMPRESS = getattr(settings, 'COMPRESS', not settings.DEBUG)
+COMPRESS_AUTO = getattr(settings, 'COMPRESS_AUTO', True)
+COMPRESS_VERSION = getattr(settings, 'COMPRESS_VERSION', False)
+COMPRESS_VERSION_PLACEHOLDER = getattr(settings, 'COMPRESS_VERSION_PLACEHOLDER', '?')
+COMPRESS_VERSION_DEFAULT = getattr(settings, 'COMPRESS_VERSION_DEFAULT', '0')
+
+COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', ['compress.filters.csstidy.CSSTidyFilter'])
+COMPRESS_JS_FILTERS = getattr(settings, 'COMPRESS_JS_FILTERS', ['compress.filters.jsmin.JSMinFilter'])
+COMPRESS_CSS = getattr(settings, 'COMPRESS_CSS', {})
+COMPRESS_JS = getattr(settings, 'COMPRESS_JS', {})
+
+if COMPRESS_CSS_FILTERS is None:
+    COMPRESS_CSS_FILTERS = []
+
+if COMPRESS_JS_FILTERS is None:
+    COMPRESS_JS_FILTERS = []
+
+if COMPRESS_VERSION and not COMPRESS_AUTO:
+    raise ImproperlyConfigured('COMPRESS_AUTO needs to be True when using COMPRESS_VERSION.')
diff --git a/apps/compress/filter_base.py b/apps/compress/filter_base.py
new file mode 100644 (file)
index 0000000..9b98531
--- /dev/null
@@ -0,0 +1,14 @@
+class FilterBase:
+    def __init__(self, verbose):
+        self.verbose = verbose
+
+    def filter_css(self, css):
+        raise NotImplementedError
+    def filter_js(self, js):
+        raise NotImplementedError
+        
+class FilterError(Exception):
+    """
+    This exception is raised when a filter fails
+    """
+    pass
\ No newline at end of file
diff --git a/apps/compress/filters/__init__.py b/apps/compress/filters/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/filters/csstidy/__init__.py b/apps/compress/filters/csstidy/__init__.py
new file mode 100644 (file)
index 0000000..d40e8ee
--- /dev/null
@@ -0,0 +1,33 @@
+import os
+import warnings
+import tempfile
+
+from django.conf import settings
+
+from compress.filter_base import FilterBase
+
+BINARY = getattr(settings, 'CSSTIDY_BINARY', 'csstidy')
+ARGUMENTS = getattr(settings, 'CSSTIDY_ARGUMENTS', '--template=highest')
+
+warnings.simplefilter('ignore', RuntimeWarning)
+
+class CSSTidyFilter(FilterBase):
+    def filter_css(self, css):
+        tmp_file = tempfile.NamedTemporaryFile(mode='w+b')
+        tmp_file.write(css)
+        tmp_file.flush()
+
+        output_file = tempfile.NamedTemporaryFile(mode='w+b')
+        
+        command = '%s %s %s %s' % (BINARY, tmp_file.name, ARGUMENTS, output_file.name)
+        
+        command_output = os.popen(command).read()
+        
+        filtered_css = output_file.read()
+        output_file.close()
+        tmp_file.close()
+        
+        if self.verbose:
+            print command_output
+        
+        return filtered_css
diff --git a/apps/compress/filters/csstidy_python/__init__.py b/apps/compress/filters/csstidy_python/__init__.py
new file mode 100644 (file)
index 0000000..7d581ed
--- /dev/null
@@ -0,0 +1,19 @@
+from django.conf import settings
+
+from compress.filter_base import FilterBase
+from compress.filters.csstidy_python.csstidy import CSSTidy
+
+COMPRESS_CSSTIDY_SETTINGS = getattr(settings, 'COMPRESS_CSSTIDY_SETTINGS', {})
+
+class CSSTidyFilter(FilterBase):
+    def filter_css(self, css):
+        tidy = CSSTidy()
+        
+        for k, v in COMPRESS_CSSTIDY_SETTINGS.items():
+            tidy.setSetting(k, v)
+
+        tidy.parse(css)
+
+        r = tidy.Output('string')
+        
+        return r
diff --git a/apps/compress/filters/csstidy_python/csstidy.py b/apps/compress/filters/csstidy_python/csstidy.py
new file mode 100644 (file)
index 0000000..6ae8dc7
--- /dev/null
@@ -0,0 +1,636 @@
+# CSSTidy - CSS Parse
+#
+# CSS Parser class
+#
+# This file is part of CSSTidy.
+#
+# CSSTidy 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 2 of the License, or
+# (at your option) any later version.
+#
+# CSSTidy 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 CSSTidy if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# @license http://opensource.org/licenses/gpl-license.php GNU Public License
+# @package csstidy
+# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
+
+import re
+
+from optimizer import CSSOptimizer
+from output import CSSPrinter
+import data
+from tools import SortedDict
+
+class CSSTidy(object):
+    #Saves the parsed CSS
+    _css = ""
+    _raw_css = SortedDict()
+    _optimized_css = SortedDict()
+
+    #List of Tokens
+    _tokens = []
+
+    #Printer class
+    _output = None
+
+    #Optimiser class
+    _optimizer = None
+
+    #Saves the CSS charset (@charset)
+    _charset = ''
+
+    #Saves all @import URLs
+    _import = []
+
+    #Saves the namespace
+    _namespace = ''
+
+    #Contains the version of csstidy
+    _version = '1.3'
+
+    #Stores the settings
+    _settings = {}
+
+    # Saves the parser-status.
+    #
+    # Possible values:
+    # - is = in selector
+    # - ip = in property
+    # - iv = in value
+    # - instr = in string (started at " or ' or ( )
+    # - ic = in comment (ignore everything)
+    # - at = in @-block
+    _status = 'is'
+
+    #Saves the current at rule (@media)
+    _at = ''
+
+    #Saves the current selector
+    _selector = ''
+
+    #Saves the current property
+    _property = ''
+
+    #Saves the position of , in selectors
+    _sel_separate = []
+
+    #Saves the current value
+    _value = ''
+
+    #Saves the current sub-value
+    _sub_value = ''
+
+    #Saves all subvalues for a property.
+    _sub_value_arr = []
+
+    #Saves the char which opened the last string
+    _str_char = ''
+    _cur_string = ''
+
+    #Status from which the parser switched to ic or instr
+    _from = ''
+
+    #Variable needed to manage string-in-strings, for example url("foo.png")
+    _str_in_str = False
+
+    #=True if in invalid at-rule
+    _invalid_at = False
+
+    #=True if something has been added to the current selector
+    _added = False
+
+    #Saves the message log
+    _log = SortedDict()
+
+    #Saves the line number
+    _line = 1
+
+    def __init__(self):
+        self._settings['remove_bslash'] = True
+        self._settings['compress_colors'] = True
+        self._settings['compress_font-weight'] = True
+        self._settings['lowercase_s'] = False
+        self._settings['optimise_shorthands'] = 2
+        self._settings['remove_last_'] = False
+        self._settings['case_properties'] = 1
+        self._settings['sort_properties'] = False
+        self._settings['sort_selectors'] = False
+        self._settings['merge_selectors'] = 2
+        self._settings['discard_invalid_properties'] = False
+        self._settings['css_level'] = 'CSS2.1'
+        self._settings['preserve_css'] = False
+        self._settings['timestamp'] = False
+        self._settings['template'] = 'highest_compression'
+
+        #Maps self._status to methods
+        self.__statusMethod = {'is':self.__parseStatus_is, 'ip': self.__parseStatus_ip, 'iv':self.__parseStatus_iv, 'instr':self.__parseStatus_instr, 'ic':self.__parseStatus_ic, 'at':self.__parseStatus_at}
+
+        self._output = CSSPrinter(self)
+        self._optimizer = CSSOptimizer(self)
+
+    #Public Methods
+    def getSetting(self, setting):
+        return self._settings.get(setting, False)
+
+    #Set the value of a setting.
+    def setSetting(self, setting, value):
+        self._settings[setting] = value
+        return True
+
+    def log(self, message, ttype, line = -1):
+        if line == -1:
+            line = self._line
+
+        line = int(line)
+
+        add = {'m': message, 't': ttype}
+
+        if not self._log.has_key(line):
+            self._log[line] = []
+            self._log[line].append(add)
+        elif add not in self._log[line]:
+            self._log[line].append(add)
+
+
+    #Checks if a character is escaped (and returns True if it is)
+    def escaped(self, string, pos):
+        return not (string[pos-1] != '\\' or self.escaped(string, pos-1))
+
+    #Adds CSS to an existing media/selector
+    def merge_css_blocks(self, media, selector, css_add):
+        for prop, value in css_add.iteritems():
+            self.__css_add_property(media, selector, prop, value, False)
+
+    #Checks if $value is !important.
+    def is_important(self, value):
+        return '!important' in value.lower()
+
+    #Returns a value without !important
+    def gvw_important(self, value):
+        if self.is_important(value):
+            ret = value.strip()
+            ret = ret[0:-9]
+            ret = ret.strip()
+            ret = ret[0:-1]
+            ret = ret.strip()
+            return ret
+
+        return value
+
+    def parse(self, cssString):
+        #Switch from \r\n to \n
+        self._css = cssString.replace("\r\n", "\n") + ' '
+        self._raw_css = {}
+        self._optimized_css = {}
+        self._curComment = ''
+
+        #Start Parsing
+        i = 0
+        while i < len(cssString):
+            if self._css[i] == "\n" or self._css[i] == "\r":
+                self._line += 1
+
+            i += self.__statusMethod[self._status](i)
+
+            i += 1;
+
+        self._optimized_css = self._optimizer.optimize(self._raw_css)
+
+    def parseFile(self, filename):
+        try:
+            f = open(filename, "r")
+            self.parse(f.read())
+        finally:
+            f.close()
+
+    #Private Methods
+    def __parseStatus_is(self, idx):
+        """
+            Parse in Selector
+        """
+        ret = 0
+
+        if self.__is_token(self._css, idx):
+            if self._css[idx] == '/' and self._css[idx+1] == '*' and self._selector.strip() == '':
+                self._status = 'ic'
+                self._from = 'is'
+                return 1
+
+            elif self._css[idx] == '@' and self._selector.strip() == '':
+                #Check for at-rule
+                self._invalid_at = True
+
+                for name, ttype in data.at_rules.iteritems():
+                    if self._css[idx+1:len(name)].lower() == name.lower():
+                        if ttype == 'at':
+                            self._at = '@' + name
+                        else:
+                            self._selector = '@' + name
+
+                        self._status = ttype
+                        self._invalid_at = False
+                        ret += len(name)
+
+                if self._invalid_at:
+                    self._selector = '@'
+                    invalid_at_name = ''
+                    for j in xrange(idx+1, len(self._css)):
+                        if not self._css[j].isalpha():
+                            break;
+
+                        invalid_at_name += self._css[j]
+
+                    self.log('Invalid @-rule: ' + invalid_at_name + ' (removed)', 'Warning')
+
+            elif self._css[idx] == '"' or self._css[idx] == "'":
+                self._cur_string = self._css[idx]
+                self._status = 'instr'
+                self._str_char = self._css[idx]
+                self._from = 'is'
+
+            elif self._invalid_at and self._css[idx] == ';':
+                self._invalid_at = False
+                self._status = 'is'
+
+            elif self._css[idx] == '{':
+                self._status = 'ip'
+                self.__add_token(data.SEL_START, self._selector)
+                self._added = False;
+
+            elif self._css[idx] == '}':
+                self.__add_token(data.AT_END, self._at)
+                self._at = ''
+                self._selector = ''
+                self._sel_separate = []
+
+            elif self._css[idx] == ',':
+                self._selector = self._selector.strip() + ','
+                self._sel_separate.append(len(self._selector))
+
+            elif self._css[idx] == '\\':
+                self._selector += self.__unicode(idx)
+
+            #remove unnecessary universal selector,  FS#147
+            elif not (self._css[idx] == '*' and self._css[idx+1] in ('.', '#', '[', ':')):
+                self._selector += self._css[idx]
+
+        else:
+            lastpos = len(self._selector)-1
+
+            if lastpos == -1 or not ((self._selector[lastpos].isspace() or self.__is_token(self._selector, lastpos) and self._selector[lastpos] == ',') and self._css[idx].isspace()):
+                self._selector += self._css[idx]
+
+        return ret
+
+    def __parseStatus_ip(self, idx):
+        """
+            Parse in property
+        """
+        if self.__is_token(self._css, idx):
+            if (self._css[idx] == ':' or self._css[idx] == '=') and self._property != '':
+                self._status = 'iv'
+
+                if not self.getSetting('discard_invalid_properties') or self.__property_is_valid(self._property):
+                    self.__add_token(data.PROPERTY, self._property)
+
+            elif self._css[idx] == '/' and self._css[idx+1] == '*' and self._property == '':
+                self._status = 'ic'
+                self._from = 'ip'
+                return 1
+
+            elif self._css[idx] == '}':
+                self.__explode_selectors()
+                self._status = 'is'
+                self._invalid_at = False
+                self.__add_token(data.SEL_END, self._selector)
+                self._selector = ''
+                self._property = ''
+
+            elif self._css[idx] == ';':
+                self._property = ''
+
+            elif self._css[idx] == '\\':
+                self._property += self.__unicode(idx)
+
+        elif not self._css[idx].isspace():
+            self._property += self._css[idx]
+
+        return 0
+
+    def __parseStatus_iv(self, idx):
+        """
+            Parse in value
+        """
+        pn = (( self._css[idx] == "\n" or self._css[idx] == "\r") and self.__property_is_next(idx+1) or idx == len(self._css)) #CHECK#
+        if self.__is_token(self._css, idx) or pn:
+            if self._css[idx] == '/' and self._css[idx+1] == '*':
+                self._status = 'ic'
+                self._from = 'iv'
+                return 1
+
+            elif self._css[idx] == '"' or self._css[idx] == "'" or self._css[idx] == '(':
+                self._cur_string = self._css[idx]
+                self._str_char = ')' if self._css[idx] == '(' else self._css[idx]
+                self._status = 'instr'
+                self._from = 'iv'
+
+            elif self._css[idx] == ',':
+                self._sub_value = self._sub_value.strip() + ','
+
+            elif self._css[idx] == '\\':
+                self._sub_value += self.__unicode(idx)
+
+            elif self._css[idx] == ';' or pn:
+                if len(self._selector) > 0 and self._selector[0] == '@' and data.at_rules.has_key(self._selector[1:]) and data.at_rules[self._selector[1:]] == 'iv':
+                    self._sub_value_arr.append(self._sub_value.strip())
+
+                    self._status = 'is'
+
+                    if '@charset' in self._selector:
+                        self._charset = self._sub_value_arr[0]
+
+                    elif '@namespace' in self._selector:
+                        self._namespace = ' '.join(self._sub_value_arr)
+
+                    elif '@import' in self._selector:
+                        self._import.append(' '.join(self._sub_value_arr))
+
+
+                    self._sub_value_arr = []
+                    self._sub_value = ''
+                    self._selector = ''
+                    self._sel_separate = []
+
+                else:
+                    self._status = 'ip'
+
+            elif self._css[idx] != '}':
+                self._sub_value += self._css[idx]
+
+            if (self._css[idx] == '}' or self._css[idx] == ';' or pn) and self._selector != '':
+                if self._at == '':
+                    self._at = data.DEFAULT_AT
+
+                #case settings
+                if self.getSetting('lowercase_s'):
+                    self._selector = self._selector.lower()
+
+                self._property = self._property.lower()
+
+                if self._sub_value != '':
+                    self._sub_value_arr.append(self._sub_value)
+                    self._sub_value = ''
+
+                self._value = ' '.join(self._sub_value_arr)
+
+
+                self._selector = self._selector.strip()
+
+                valid = self.__property_is_valid(self._property)
+
+                if (not self._invalid_at or self.getSetting('preserve_css')) and (not self.getSetting('discard_invalid_properties') or valid):
+                    self.__css_add_property(self._at, self._selector, self._property, self._value)
+                    self.__add_token(data.VALUE, self._value)
+
+                if not valid:
+                    if self.getSetting('discard_invalid_properties'):
+                        self.log('Removed invalid property: ' + self._property, 'Warning')
+
+                    else:
+                        self.log('Invalid property in ' + self.getSetting('css_level').upper() + ': ' + self._property, 'Warning')
+
+                self._property = '';
+                self._sub_value_arr = []
+                self._value = ''
+
+            if self._css[idx] == '}':
+                self.__explode_selectors()
+                self.__add_token(data.SEL_END, self._selector)
+                self._status = 'is'
+                self._invalid_at = False
+                self._selector = ''
+
+        elif not pn:
+            self._sub_value += self._css[idx]
+
+            if self._css[idx].isspace():
+                if self._sub_value != '':
+                    self._sub_value_arr.append(self._sub_value)
+                    self._sub_value = ''
+
+        return 0
+
+    def __parseStatus_instr(self, idx):
+        """
+            Parse in String
+        """
+        if self._str_char == ')' and (self._css[idx] == '"' or self._css[idx] == "'") and not self.escaped(self._css, idx):
+            self._str_in_str = not self._str_in_str
+
+        temp_add = self._css[idx] # ...and no not-escaped backslash at the previous position
+        if (self._css[idx] == "\n" or self._css[idx] == "\r") and not (self._css[idx-1] == '\\' and not self.escaped(self._css, idx-1)):
+            temp_add = "\\A "
+            self.log('Fixed incorrect newline in string', 'Warning')
+
+        if not (self._str_char == ')' and self._css[idx].isspace() and not self._str_in_str):
+            self._cur_string += temp_add
+
+        if self._css[idx] == self._str_char and not self.escaped(self._css, idx) and not self._str_in_str:
+            self._status = self._from
+            regex = re.compile(r'([\s]+)', re.I | re.U | re.S)
+            if regex.match(self._cur_string) is None and self._property != 'content':
+                if self._str_char == '"' or self._str_char == "'":
+                    self._cur_string = self._cur_string[1:-1]
+
+                elif len(self._cur_string) > 3 and (self._cur_string[1] == '"' or self._cur_string[1] == "'"):
+                    self._cur_string = self._cur_string[0] + self._cur_string[2:-2] + self._cur_string[-1]
+
+            if self._from == 'iv':
+                self._sub_value += self._cur_string
+
+            elif self._from == 'is':
+                self._selector += self._cur_string
+
+        return 0
+
+    def __parseStatus_ic(self, idx):
+        """
+            Parse css In Comment
+        """
+        if self._css[idx] == '*' and self._css[idx+1] == '/':
+            self._status = self._from
+            self.__add_token(data.COMMENT, self._curComment)
+            self._curComment = ''
+            return 1
+
+        else:
+            self._curComment += self._css[idx]
+
+        return 0
+
+    def __parseStatus_at(self, idx):
+        """
+            Parse in at-block
+        """
+        if self.__is_token(string, idx):
+            if self._css[idx] == '/' and self._css[idx+1] == '*':
+                self._status = 'ic'
+                self._from = 'at'
+                return 1
+
+            elif self._css[i] == '{':
+                self._status = 'is'
+                self.__add_token(data.AT_START, self._at)
+
+            elif self._css[i] == ',':
+                self._at = self._at.strip() + ','
+
+            elif self._css[i] == '\\':
+                self._at += self.__unicode(i)
+        else:
+            lastpos = len(self._at)-1
+            if not (self._at[lastpos].isspace() or self.__is_token(self._at, lastpos) and self._at[lastpos] == ',') and self._css[i].isspace():
+                self._at += self._css[i]
+
+        return 0
+
+    def __explode_selectors(self):
+        #Explode multiple selectors
+        if self.getSetting('merge_selectors') == 1:
+            new_sels = []
+            lastpos = 0;
+            self._sel_separate.append(len(self._selector))
+
+            for num in xrange(len(self._sel_separate)):
+                pos = self._sel_separate[num]
+                if num == (len(self._sel_separate)): #CHECK#
+                    pos += 1
+
+                new_sels.append(self._selector[lastpos:(pos-lastpos-1)])
+                lastpos = pos
+
+            if len(new_sels) > 1:
+                for selector in new_sels:
+                    self.merge_css_blocks(self._at, selector, self._raw_css[self._at][self._selector])
+
+                del self._raw_css[self._at][self._selector]
+
+        self._sel_separate = []
+
+    #Adds a property with value to the existing CSS code
+    def __css_add_property(self, media, selector, prop, new_val):
+        if self.getSetting('preserve_css') or new_val.strip() == '':
+            return
+
+        if not self._raw_css.has_key(media):
+            self._raw_css[media] = SortedDict()
+
+        if not self._raw_css[media].has_key(selector):
+            self._raw_css[media][selector] = SortedDict()
+
+        self._added = True
+        if self._raw_css[media][selector].has_key(prop):
+            if (self.is_important(self._raw_css[media][selector][prop]) and self.is_important(new_val)) or not self.is_important(self._raw_css[media][selector][prop]):
+                del self._raw_css[media][selector][prop]
+                self._raw_css[media][selector][prop] = new_val.strip()
+
+        else:
+            self._raw_css[media][selector][prop] = new_val.strip()
+
+    #Checks if the next word in a string from pos is a CSS property
+    def __property_is_next(self, pos):
+        istring = self._css[pos: len(self._css)]
+        pos = istring.find(':')
+        if pos == -1:
+            return False;
+
+        istring = istring[:pos].strip().lower()
+        if data.all_properties.has_key(istring):
+            self.log('Added semicolon to the end of declaration', 'Warning')
+            return True
+
+        return False;
+
+    #Checks if a property is valid
+    def __property_is_valid(self, prop):
+        return (data.all_properties.has_key(prop) and data.all_properties[prop].find(self.getSetting('css_level').upper()) != -1)
+
+    #Adds a token to self._tokens
+    def __add_token(self, ttype, cssdata, do=False):
+        if self.getSetting('preserve_css') or do:
+            if ttype == data.COMMENT:
+                token = [ttype, cssdata]
+            else:
+                token = [ttype, cssdata.strip()]
+
+            self._tokens.append(token)
+
+    #Parse unicode notations and find a replacement character
+    def __unicode(self, idx):
+       ##FIX##
+       return ''
+
+    #Starts parsing from URL
+    ##USED?
+    def __parse_from_url(self, url):
+        try:
+            if "http" in url.lower() or "https" in url.lower():
+                f = urllib.urlopen(url)
+            else:
+                f = open(url)
+
+            data = f.read()
+            return self.parse(data)
+        finally:
+            f.close()
+
+    #Checks if there is a token at the current position
+    def __is_token(self, string, idx):
+        return (string[idx] in data.tokens and not self.escaped(string, idx))
+
+
+    #Property Methods
+    def _getOutput(self):
+        self._output.prepare(self._optimized_css)
+        return self._output.render
+
+    def _getLog(self):
+        ret = ""
+        ks = self._log.keys()
+        ks.sort()
+        for line in ks:
+            for msg in self._log[line]:
+                ret += "Type: " + msg['t'] + "\n"
+                ret += "Message: " + msg['m'] + "\n"
+            ret += "\n"
+
+        return ret
+
+    def _getCSS(self):
+        return self._css
+
+
+    #Properties
+    Output = property(_getOutput, None)
+    Log = property(_getLog, None)
+    CSS = property(_getCSS, None)
+
+
+if __name__ == '__main__':
+    import sys
+    tidy = CSSTidy()
+    f = open(sys.argv[1], "r")
+    css = f.read()
+    f.close()
+    tidy.parse(css)
+    tidy.Output('file', filename="Stylesheet.min.css")
+    print tidy.Output()
+    #print tidy._import
\ No newline at end of file
diff --git a/apps/compress/filters/csstidy_python/data.py b/apps/compress/filters/csstidy_python/data.py
new file mode 100644 (file)
index 0000000..bd728cb
--- /dev/null
@@ -0,0 +1,421 @@
+# Various CSS Data for CSSTidy
+#
+# This file is part of CSSTidy.
+#
+# CSSTidy 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 2 of the License, or
+# (at your option) any later version.
+#
+# CSSTidy 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 CSSTidy; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# @license http://opensource.org/licenses/gpl-license.php GNU Public License
+# @package csstidy
+# @author Florian Schmitz (floele at gmail dot com) 2005
+
+AT_START    = 1
+AT_END      = 2
+SEL_START   = 3
+SEL_END     = 4
+PROPERTY    = 5
+VALUE       = 6
+COMMENT     = 7
+DEFAULT_AT  = 41
+
+# All whitespace allowed in CSS
+#
+# @global array whitespace
+# @version 1.0
+whitespace = frozenset([' ',"\n","\t","\r","\x0B"])
+
+# All CSS tokens used by csstidy
+#
+# @global string tokens
+# @version 1.0
+tokens = '/@}{;:=\'"(,\\!$%&)#+.<>?[]^`|~'
+
+# All CSS units (CSS 3 units included)
+#
+# @see compress_numbers()
+# @global array units
+# @version 1.0
+units = frozenset(['in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz'])
+
+# Available at-rules
+#
+# @global array at_rules
+# @version 1.0
+at_rules = {'page':'is', 'font-face':'is', 'charset':'iv', 'import':'iv', 'namespace':'iv', 'media':'at'}
+
+# Properties that need a value with unit
+#
+# @todo CSS3 properties
+# @see compress_numbers()
+# @global array unit_values
+# @version 1.2
+unit_values = frozenset(['background', 'background-position', 'border', 'border-top', 'border-right', 'border-bottom',
+                                    'border-left', 'border-width', 'border-top-width', 'border-right-width', 'border-left-width',
+                                    'border-bottom-width', 'bottom', 'border-spacing', 'font-size','height', 'left', 'margin', 'margin-top',
+                                    'margin-right', 'margin-bottom', 'margin-left', 'max-height', 'max-width', 'min-height', 'min-width',
+                                    'outline-width', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left','position',
+                                    'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width'
+                                    ])
+
+
+# Properties that allow <color> as value
+#
+# @todo CSS3 properties
+# @see compress_numbers()
+# @global array color_values
+# @version 1.0
+color_values = frozenset(['background-color', 'border-color', 'border-top-color', 'border-right-color',
+                                        'border-bottom-color', 'border-left-color', 'color', 'outline-color'])
+
+
+# Default values for the background properties
+#
+# @todo Possibly property names will change during CSS3 development
+# @global array background_prop_default
+# @see dissolve_short_bg()
+# @see merge_bg()
+# @version 1.0
+background_prop_default = {}
+background_prop_default['background-image'] = 'none'
+background_prop_default['background-size'] = 'auto'
+background_prop_default['background-repeat'] = 'repeat'
+background_prop_default['background-position'] = '0 0'
+background_prop_default['background-attachment'] = 'scroll'
+background_prop_default['background-clip'] = 'border'
+background_prop_default['background-origin'] = 'padding'
+background_prop_default['background-color'] = 'transparent'
+
+# A list of non-W3C color names which get replaced by their hex-codes
+#
+# @global array replace_colors
+# @see cut_color()
+# @version 1.0
+replace_colors = {}
+replace_colors['aliceblue'] = '#F0F8FF'
+replace_colors['antiquewhite'] = '#FAEBD7'
+replace_colors['aquamarine'] = '#7FFFD4'
+replace_colors['azure'] = '#F0FFFF'
+replace_colors['beige'] = '#F5F5DC'
+replace_colors['bisque'] = '#FFE4C4'
+replace_colors['blanchedalmond'] = '#FFEBCD'
+replace_colors['blueviolet'] = '#8A2BE2'
+replace_colors['brown'] = '#A52A2A'
+replace_colors['burlywood'] = '#DEB887'
+replace_colors['cadetblue'] = '#5F9EA0'
+replace_colors['chartreuse'] = '#7FFF00'
+replace_colors['chocolate'] = '#D2691E'
+replace_colors['coral'] = '#FF7F50'
+replace_colors['cornflowerblue'] = '#6495ED'
+replace_colors['cornsilk'] = '#FFF8DC'
+replace_colors['crimson'] = '#DC143C'
+replace_colors['cyan'] = '#00FFFF'
+replace_colors['darkblue'] = '#00008B'
+replace_colors['darkcyan'] = '#008B8B'
+replace_colors['darkgoldenrod'] = '#B8860B'
+replace_colors['darkgray'] = '#A9A9A9'
+replace_colors['darkgreen'] = '#006400'
+replace_colors['darkkhaki'] = '#BDB76B'
+replace_colors['darkmagenta'] = '#8B008B'
+replace_colors['darkolivegreen'] = '#556B2F'
+replace_colors['darkorange'] = '#FF8C00'
+replace_colors['darkorchid'] = '#9932CC'
+replace_colors['darkred'] = '#8B0000'
+replace_colors['darksalmon'] = '#E9967A'
+replace_colors['darkseagreen'] = '#8FBC8F'
+replace_colors['darkslateblue'] = '#483D8B'
+replace_colors['darkslategray'] = '#2F4F4F'
+replace_colors['darkturquoise'] = '#00CED1'
+replace_colors['darkviolet'] = '#9400D3'
+replace_colors['deeppink'] = '#FF1493'
+replace_colors['deepskyblue'] = '#00BFFF'
+replace_colors['dimgray'] = '#696969'
+replace_colors['dodgerblue'] = '#1E90FF'
+replace_colors['feldspar'] = '#D19275'
+replace_colors['firebrick'] = '#B22222'
+replace_colors['floralwhite'] = '#FFFAF0'
+replace_colors['forestgreen'] = '#228B22'
+replace_colors['gainsboro'] = '#DCDCDC'
+replace_colors['ghostwhite'] = '#F8F8FF'
+replace_colors['gold'] = '#FFD700'
+replace_colors['goldenrod'] = '#DAA520'
+replace_colors['greenyellow'] = '#ADFF2F'
+replace_colors['honeydew'] = '#F0FFF0'
+replace_colors['hotpink'] = '#FF69B4'
+replace_colors['indianred'] = '#CD5C5C'
+replace_colors['indigo'] = '#4B0082'
+replace_colors['ivory'] = '#FFFFF0'
+replace_colors['khaki'] = '#F0E68C'
+replace_colors['lavender'] = '#E6E6FA'
+replace_colors['lavenderblush'] = '#FFF0F5'
+replace_colors['lawngreen'] = '#7CFC00'
+replace_colors['lemonchiffon'] = '#FFFACD'
+replace_colors['lightblue'] = '#ADD8E6'
+replace_colors['lightcoral'] = '#F08080'
+replace_colors['lightcyan'] = '#E0FFFF'
+replace_colors['lightgoldenrodyellow'] = '#FAFAD2'
+replace_colors['lightgrey'] = '#D3D3D3'
+replace_colors['lightgreen'] = '#90EE90'
+replace_colors['lightpink'] = '#FFB6C1'
+replace_colors['lightsalmon'] = '#FFA07A'
+replace_colors['lightseagreen'] = '#20B2AA'
+replace_colors['lightskyblue'] = '#87CEFA'
+replace_colors['lightslateblue'] = '#8470FF'
+replace_colors['lightslategray'] = '#778899'
+replace_colors['lightsteelblue'] = '#B0C4DE'
+replace_colors['lightyellow'] = '#FFFFE0'
+replace_colors['limegreen'] = '#32CD32'
+replace_colors['linen'] = '#FAF0E6'
+replace_colors['magenta'] = '#FF00FF'
+replace_colors['mediumaquamarine'] = '#66CDAA'
+replace_colors['mediumblue'] = '#0000CD'
+replace_colors['mediumorchid'] = '#BA55D3'
+replace_colors['mediumpurple'] = '#9370D8'
+replace_colors['mediumseagreen'] = '#3CB371'
+replace_colors['mediumslateblue'] = '#7B68EE'
+replace_colors['mediumspringgreen'] = '#00FA9A'
+replace_colors['mediumturquoise'] = '#48D1CC'
+replace_colors['mediumvioletred'] = '#C71585'
+replace_colors['midnightblue'] = '#191970'
+replace_colors['mintcream'] = '#F5FFFA'
+replace_colors['mistyrose'] = '#FFE4E1'
+replace_colors['moccasin'] = '#FFE4B5'
+replace_colors['navajowhite'] = '#FFDEAD'
+replace_colors['oldlace'] = '#FDF5E6'
+replace_colors['olivedrab'] = '#6B8E23'
+replace_colors['orangered'] = '#FF4500'
+replace_colors['orchid'] = '#DA70D6'
+replace_colors['palegoldenrod'] = '#EEE8AA'
+replace_colors['palegreen'] = '#98FB98'
+replace_colors['paleturquoise'] = '#AFEEEE'
+replace_colors['palevioletred'] = '#D87093'
+replace_colors['papayawhip'] = '#FFEFD5'
+replace_colors['peachpuff'] = '#FFDAB9'
+replace_colors['peru'] = '#CD853F'
+replace_colors['pink'] = '#FFC0CB'
+replace_colors['plum'] = '#DDA0DD'
+replace_colors['powderblue'] = '#B0E0E6'
+replace_colors['rosybrown'] = '#BC8F8F'
+replace_colors['royalblue'] = '#4169E1'
+replace_colors['saddlebrown'] = '#8B4513'
+replace_colors['salmon'] = '#FA8072'
+replace_colors['sandybrown'] = '#F4A460'
+replace_colors['seagreen'] = '#2E8B57'
+replace_colors['seashell'] = '#FFF5EE'
+replace_colors['sienna'] = '#A0522D'
+replace_colors['skyblue'] = '#87CEEB'
+replace_colors['slateblue'] = '#6A5ACD'
+replace_colors['slategray'] = '#708090'
+replace_colors['snow'] = '#FFFAFA'
+replace_colors['springgreen'] = '#00FF7F'
+replace_colors['steelblue'] = '#4682B4'
+replace_colors['tan'] = '#D2B48C'
+replace_colors['thistle'] = '#D8BFD8'
+replace_colors['tomato'] = '#FF6347'
+replace_colors['turquoise'] = '#40E0D0'
+replace_colors['violet'] = '#EE82EE'
+replace_colors['violetred'] = '#D02090'
+replace_colors['wheat'] = '#F5DEB3'
+replace_colors['whitesmoke'] = '#F5F5F5'
+replace_colors['yellowgreen'] = '#9ACD32'
+
+#A list of optimized colors
+optimize_colors = {}
+optimize_colors['black'] = '#000'
+optimize_colors['fuchsia'] = '#F0F'
+optimize_colors['white'] = '#FFF'
+optimize_colors['yellow'] = '#FF0'
+optimize_colors['cyan'] = '#0FF'
+optimize_colors['magenta'] = '#F0F'
+optimize_colors['lightslategray'] = '#789'
+
+optimize_colors['#800000'] = 'maroon'
+optimize_colors['#FFA500'] = 'orange'
+optimize_colors['#808000'] = 'olive'
+optimize_colors['#800080'] = 'purple'
+optimize_colors['#008000'] = 'green'
+optimize_colors['#000080'] = 'navy'
+optimize_colors['#008080'] = 'teal'
+optimize_colors['#C0C0C0'] = 'silver'
+optimize_colors['#808080'] = 'gray'
+optimize_colors['#4B0082'] = 'indigo'
+optimize_colors['#FFD700'] = 'gold'
+optimize_colors['#A52A2A'] = 'brown'
+optimize_colors['#00FFFF'] = 'cyan'
+optimize_colors['#EE82EE'] = 'violet'
+optimize_colors['#DA70D6'] = 'orchid'
+optimize_colors['#FFE4C4'] = 'bisque'
+optimize_colors['#F0E68C'] = 'khaki'
+optimize_colors['#F5DEB3'] = 'wheat'
+optimize_colors['#FF7F50'] = 'coral'
+optimize_colors['#F5F5DC'] = 'beige'
+optimize_colors['#F0FFFF'] = 'azure'
+optimize_colors['#A0522D'] = 'sienna'
+optimize_colors['#CD853F'] = 'peru'
+optimize_colors['#FFFFF0'] = 'ivory'
+optimize_colors['#DDA0DD'] = 'plum'
+optimize_colors['#D2B48C'] = 'tan'
+optimize_colors['#FFC0CB'] = 'pink'
+optimize_colors['#FFFAFA'] = 'snow'
+optimize_colors['#FA8072'] = 'salmon'
+optimize_colors['#FF6347'] = 'tomato'
+optimize_colors['#FAF0E6'] = 'linen'
+optimize_colors['#F00'] = 'red'
+
+
+# A list of all shorthand properties that are devided into four properties and/or have four subvalues
+#
+# @global array shorthands
+# @todo Are there new ones in CSS3?
+# @see dissolve_4value_shorthands()
+# @see merge_4value_shorthands()
+# @version 1.0
+shorthands = {}
+shorthands['border-color'] = ['border-top-color','border-right-color','border-bottom-color','border-left-color']
+shorthands['border-style'] = ['border-top-style','border-right-style','border-bottom-style','border-left-style']
+shorthands['border-width'] = ['border-top-width','border-right-width','border-bottom-width','border-left-width']
+shorthands['margin'] = ['margin-top','margin-right','margin-bottom','margin-left']
+shorthands['padding'] = ['padding-top','padding-right','padding-bottom','padding-left']
+shorthands['-moz-border-radius'] = 0
+
+# All CSS Properties. Needed for csstidy::property_is_next()
+#
+# @global array all_properties
+# @todo Add CSS3 properties
+# @version 1.0
+# @see csstidy::property_is_next()
+all_properties = {}
+all_properties['background'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['background-color'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['background-image'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['background-position'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-top'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-right'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-left'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-color'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-top-color'] = 'CSS2.0,CSS2.1'
+all_properties['border-bottom-color'] = 'CSS2.0,CSS2.1'
+all_properties['border-left-color'] = 'CSS2.0,CSS2.1'
+all_properties['border-right-color'] = 'CSS2.0,CSS2.1'
+all_properties['border-style'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-top-style'] = 'CSS2.0,CSS2.1'
+all_properties['border-right-style'] = 'CSS2.0,CSS2.1'
+all_properties['border-left-style'] = 'CSS2.0,CSS2.1'
+all_properties['border-bottom-style'] = 'CSS2.0,CSS2.1'
+all_properties['border-width'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['border-collapse'] = 'CSS2.0,CSS2.1'
+all_properties['border-spacing'] = 'CSS2.0,CSS2.1'
+all_properties['bottom'] = 'CSS2.0,CSS2.1'
+all_properties['caption-side'] = 'CSS2.0,CSS2.1'
+all_properties['content'] = 'CSS2.0,CSS2.1'
+all_properties['clear'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['clip'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['color'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['counter-reset'] = 'CSS2.0,CSS2.1'
+all_properties['counter-increment'] = 'CSS2.0,CSS2.1'
+all_properties['cursor'] = 'CSS2.0,CSS2.1'
+all_properties['empty-cells'] = 'CSS2.0,CSS2.1'
+all_properties['display'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['direction'] = 'CSS2.0,CSS2.1'
+all_properties['float'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['font'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['font-family'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['font-style'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['font-stretch'] = 'CSS2.0'
+all_properties['font-size-adjust'] = 'CSS2.0'
+all_properties['font-size'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['height'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['left'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['line-height'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['list-style'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['margin'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['marks'] = 'CSS1.0,CSS2.0'
+all_properties['marker-offset'] = 'CSS2.0'
+all_properties['max-height'] = 'CSS2.0,CSS2.1'
+all_properties['max-width'] = 'CSS2.0,CSS2.1'
+all_properties['min-height'] = 'CSS2.0,CSS2.1'
+all_properties['min-width'] = 'CSS2.0,CSS2.1'
+all_properties['overflow'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['orphans'] = 'CSS2.0,CSS2.1'
+all_properties['outline'] = 'CSS2.0,CSS2.1'
+all_properties['outline-width'] = 'CSS2.0,CSS2.1'
+all_properties['outline-style'] = 'CSS2.0,CSS2.1'
+all_properties['outline-color'] = 'CSS2.0,CSS2.1'
+all_properties['padding'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['page-break-inside'] = 'CSS2.0,CSS2.1'
+all_properties['page'] = 'CSS2.0'
+all_properties['position'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['quotes'] = 'CSS2.0,CSS2.1'
+all_properties['right'] = 'CSS2.0,CSS2.1'
+all_properties['size'] = 'CSS1.0,CSS2.0'
+all_properties['speak-header'] = 'CSS2.0,CSS2.1'
+all_properties['table-layout'] = 'CSS2.0,CSS2.1'
+all_properties['top'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['text-align'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['text-shadow'] = 'CSS2.0'
+all_properties['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['white-space'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['unicode-bidi'] = 'CSS2.0,CSS2.1'
+all_properties['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['visibility'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['width'] = 'CSS1.0,CSS2.0,CSS2.1'
+all_properties['widows'] = 'CSS2.0,CSS2.1'
+all_properties['z-index'] = 'CSS1.0,CSS2.0,CSS2.1'
+
+# Speech #
+all_properties['volume'] = 'CSS2.0,CSS2.1'
+all_properties['speak'] = 'CSS2.0,CSS2.1'
+all_properties['pause'] = 'CSS2.0,CSS2.1'
+all_properties['pause-before'] = 'CSS2.0,CSS2.1'
+all_properties['pause-after'] = 'CSS2.0,CSS2.1'
+all_properties['cue'] = 'CSS2.0,CSS2.1'
+all_properties['cue-before'] = 'CSS2.0,CSS2.1'
+all_properties['cue-after'] = 'CSS2.0,CSS2.1'
+all_properties['play-during'] = 'CSS2.0,CSS2.1'
+all_properties['azimuth'] = 'CSS2.0,CSS2.1'
+all_properties['elevation'] = 'CSS2.0,CSS2.1'
+all_properties['speech-rate'] = 'CSS2.0,CSS2.1'
+all_properties['voice-family'] = 'CSS2.0,CSS2.1'
+all_properties['pitch'] = 'CSS2.0,CSS2.1'
+all_properties['pitch-range'] = 'CSS2.0,CSS2.1'
+all_properties['stress'] = 'CSS2.0,CSS2.1'
+all_properties['richness'] = 'CSS2.0,CSS2.1'
+all_properties['speak-punctuation'] = 'CSS2.0,CSS2.1'
+all_properties['speak-numeral'] = 'CSS2.0,CSS2.1'
\ No newline at end of file
diff --git a/apps/compress/filters/csstidy_python/optimizer.py b/apps/compress/filters/csstidy_python/optimizer.py
new file mode 100644 (file)
index 0000000..7cd284c
--- /dev/null
@@ -0,0 +1,383 @@
+# CSSTidy - CSS Optimizer
+#
+# CSS Optimizer class
+#
+# This file is part of CSSTidy.
+#
+# CSSTidy 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 2 of the License, or
+# (at your option) any later version.
+#
+# CSSTidy 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 CSSTidy if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# @license http://opensource.org/licenses/gpl-license.php GNU Public License
+# @package csstidy
+# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
+
+import data
+from tools import SortedDict
+
+
+class CSSOptimizer(object):
+    def __init__(self, parser):
+        #raw_css is a dict
+        self.parser = parser
+        self._optimized_css = SortedDict
+
+
+#PUBLIC METHODS
+    def optimize(self, raw_css):
+        if self.parser.getSetting('preserve_css'):
+            return raw_css
+
+        self._optimized_css = raw_css
+
+        if self.parser.getSetting('merge_selectors') == 2:
+            self.__merge_selectors()
+
+        ##OPTIMIZE##
+        for media, css in self._optimized_css.iteritems():
+            for selector, cssdata in css.iteritems():
+                if self.parser.getSetting('optimise_shorthands') >= 1:
+                    cssdata = self.__merge_4value_shorthands(cssdata)
+
+                if self.parser.getSetting('optimise_shorthands') >= 2:
+                    cssdata = self.__merge_bg(cssdata)
+
+                for item, value in cssdata.iteritems():
+                    value = self.__compress_numbers(item, value)
+                    value = self.__compress_important(value)
+
+                    if item in data.color_values and self.parser.getSetting('compress_colors'):
+                        old = value[:]
+                        value = self.__compress_color(value)
+                        if old != value:
+                            self.parser.log('In "' + selector + '" Optimised ' + item + ': Changed ' + old + ' to ' + value, 'Information')
+
+                    if item == 'font-weight' and self.parser.getSetting('compress_font-weight'):
+                        if value  == 'bold':
+                            value = '700'
+                            self.parser.log('In "' + selector + '" Optimised font-weight: Changed "bold" to "700"', 'Information')
+
+                        elif value == 'normal':
+                            value = '400'
+                            self.parser.log('In "' + selector + '" Optimised font-weight: Changed "normal" to "400"', 'Information')
+
+                    self._optimized_css[media][selector][item] = value
+
+
+        return self._optimized_css
+
+
+#PRIVATE METHODS
+    def __merge_bg(self, cssdata):
+        """
+            Merges all background properties
+            @cssdata (dict) is a dictionary of the selector properties
+        """
+        #Max number of background images. CSS3 not yet fully implemented
+        img = 1
+        clr = 1
+        bg_img_list = []
+        if cssdata.has_key('background-image'):
+            img = len(cssdata['background-image'].split(','))
+            bg_img_list = self.parser.gvw_important(cssdata['background-image']).split(',')
+
+        elif cssdata.has_key('background-color'):
+            clr = len(cssdata['background-color'].split(','))
+
+
+        number_of_values = max(img, clr, 1)
+
+        new_bg_value = ''
+        important = ''
+
+        for i in xrange(number_of_values):
+            for bg_property, default_value in data.background_prop_default.iteritems():
+                #Skip if property does not exist
+                if not cssdata.has_key(bg_property):
+                    continue
+
+                cur_value = cssdata[bg_property]
+
+                #Skip some properties if there is no background image
+                if (len(bg_img_list) > i and bg_img_list[i] == 'none') and bg_property in frozenset(['background-size', 'background-position', 'background-attachment', 'background-repeat']):
+                    continue
+
+                #Remove !important
+                if self.parser.is_important(cur_value):
+                    important = ' !important'
+                    cur_value = self.parser.gvw_important(cur_value)
+
+                #Do not add default values
+                if cur_value == default_value:
+                    continue
+
+                temp = cur_value.split(',')
+
+                if len(temp) > i:
+                    if bg_property == 'background-size':
+                        new_bg_value += '(' + temp[i] + ') '
+
+                    else:
+                        new_bg_value += temp[i] + ' '
+
+            new_bg_value = new_bg_value.strip()
+            if i != (number_of_values-1):
+                new_bg_value += ','
+
+        #Delete all background-properties
+        for bg_property, default_value in data.background_prop_default.iteritems():
+            try:
+                del cssdata[bg_property]
+            except:
+                pass
+
+        #Add new background property
+        if new_bg_value != '':
+            cssdata['background'] = new_bg_value + important
+
+        return cssdata
+
+    def __merge_4value_shorthands(self, cssdata):
+        """
+            Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
+            @cssdata (dict) is a dictionary of the selector properties
+        """
+        for key, value in data.shorthands.iteritems():
+            important = ''
+            if value != 0 and cssdata.has_key(value[0]) and cssdata.has_key(value[1]) and cssdata.has_key(value[2]) and cssdata.has_key(value[3]):
+                cssdata[key] = ''
+
+                for i in xrange(4):
+                    val = cssdata[value[i]]
+                    if self.parser.is_important(val):
+                        important = '!important'
+                        cssdata[key] += self.parser.gvw_important(val) + ' '
+
+                    else:
+                        cssdata[key] += val + ' '
+
+                    del cssdata[value[i]]
+            if cssdata.has_key(key):
+                cssdata[key] = self.__shorthand(cssdata[key] + important.strip())
+
+        return cssdata
+
+
+    def __merge_selectors(self):
+        """
+            Merges selectors with same properties. Example: a{color:red} b{color:red} . a,b{color:red}
+            Very basic and has at least one bug. Hopefully there is a replacement soon.
+            @selector_one (string) is the current selector
+            @value_one (dict) is a dictionary of the selector properties
+            Note: Currently is the elements of a selector are identical, but in a different order, they are not merged
+        """
+
+        ##OPTIMIZE##
+        ##FIX##
+
+        raw_css = self._optimized_css.copy()
+        delete = []
+        add = SortedDict()
+        for media, css in raw_css.iteritems():
+            for selector_one, value_one in css.iteritems():
+                newsel = selector_one
+
+                for selector_two, value_two in css.iteritems():
+                    if selector_one == selector_two:
+                        #We need to skip self
+                        continue
+
+                    if value_one == value_two:
+                        #Ok, we need to merge these two selectors
+                        newsel += ', ' + selector_two
+                        delete.append((media, selector_two))
+
+
+        if not add.has_key(media):
+            add[media] = SortedDict()
+
+        add[media][newsel] = value_one
+        delete.append((media, selector_one))
+
+        for item in delete:
+            try:
+                del self._optimized_css[item[0]][item[1]]
+            except:
+                #Must have already been deleted
+                continue
+
+        for media, css in add.iteritems():
+            self._optimized_css[media].update(css)
+
+
+
+    def __shorthand(self, value):
+        """
+            Compresses shorthand values. Example: margin:1px 1px 1px 1px . margin:1px
+            @value (string)
+        """
+
+        ##FIX##
+
+        important = '';
+        if self.parser.is_important(value):
+            value_list = self.parser.gvw_important(value)
+            important = '!important'
+        else:
+            value_list = value
+
+        ret = value
+        value_list = value_list.split(' ')
+
+        if len(value_list) == 4:
+            if value_list[0] == value_list[1] and value_list[0] == value_list[2] and value_list[0] == value_list[3]:
+                ret = value_list[0] + important
+
+            elif value_list[1] == value_list[3] and value_list[0] == value_list[2]:
+                ret = value_list[0] + ' ' + value_list[1] + important
+
+            elif value_list[1] == value_list[3]:
+                ret = value_list[0] + ' ' + value_list[1] + ' ' + value_list[2] + important
+
+        elif len(value_list) == 3:
+            if value_list[0] == value_list[1] and value_list[0] == value_list[2]:
+                ret = value_list[0] + important
+
+            elif value_list[0] == value_list[2]:
+                return value_list[0] + ' ' + value_list[1] + important
+
+        elif len(value_list) == 2:
+            if value_list[0] == value_list[1]:
+                ret = value_list[0] + important
+
+        if ret != value:
+            self.parser.log('Optimised shorthand notation: Changed "' + value + '" to "' + ret + '"', 'Information')
+
+        return ret
+
+    def __compress_important(self, value):
+        """
+            Removes unnecessary whitespace in ! important
+            @value (string)
+        """
+        if self.parser.is_important(value):
+            value = self.parser.gvw_important(value) + '!important'
+
+        return value
+
+    def __compress_numbers(self, prop, value):
+        """
+            Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
+            @value (string) is the posible number to be compressed
+        """
+
+        ##FIX##
+
+        value = value.split('/')
+
+        for l in xrange(len(value)):
+            #continue if no numeric value
+            if not (len(value[l]) > 0 and (value[l][0].isdigit() or value[l][0] in ('+', '-') )):
+                continue
+
+            #Fix bad colors
+            if prop in data.color_values:
+                value[l] = '#' + value[l]
+
+            is_floatable = False
+            try:
+                float(value[l])
+                is_floatable = True
+            except:
+                pass
+
+            if is_floatable and float(value[l]) == 0:
+                value[l] = '0'
+
+            elif value[l][0] != '#':
+                unit_found = False
+                for unit in data.units:
+                    pos = value[l].lower().find(unit)
+                    if pos != -1 and prop not in data.shorthands:
+                        value[l] = self.__remove_leading_zeros(float(value[l][:pos])) + unit
+                        unit_found = True
+                        break;
+
+                if not unit_found and prop in data.unit_values and prop not in data.shorthands:
+                    value[l] = self.__remove_leading_zeros(float(value[l])) + 'px'
+
+                elif not unit_found and prop not in data.shorthands:
+                    value[l] = self.__remove_leading_zeros(float(value[l]))
+
+
+        if len(value) > 1:
+            return '/'.join(value)
+
+        return value[0]
+
+    def __remove_leading_zeros(self, float_val):
+        """
+            Removes the leading zeros from a float value
+            @float_val (float)
+            @returns (string)
+        """
+        #Remove leading zero
+        if abs(float_val) < 1:
+            if float_val < 0:
+                float_val = '-' . str(float_val)[2:]
+            else:
+                float_val = str(float_val)[1:]
+
+        return str(float_val)
+
+    def __compress_color(self, color):
+        """
+            Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
+            @color (string) the {posible} color to change
+        """
+
+        #rgb(0,0,0) . #000000 (or #000 in this case later)
+        if color[:4].lower() == 'rgb(':
+            color_tmp = color[4:(len(color)-5)]
+            color_tmp = color_tmp.split(',')
+
+            for c in color_tmp:
+                c = c.strip()
+                if c[:-1] == '%':
+                    c = round((255*color_tmp[i])/100)
+
+                if color_tmp[i] > 255:
+                    color_tmp[i] = 255
+
+            color = '#'
+
+            for i in xrange(3):
+                if color_tmp[i] < 16:
+                    color += '0' + str(hex(color_tmp[i])).replace('0x', '')
+                else:
+                    color += str(hex(color_tmp[i])).replace('0x', '')
+
+        #Fix bad color names
+        if data.replace_colors.has_key(color.lower()):
+            color = data.replace_colors[color.lower()]
+
+        #aabbcc . #abc
+        if len(color) == 7:
+            color_temp = color.lower()
+            if color_temp[0] == '#' and color_temp[1] == color_temp[2] and color_temp[3] == color_temp[4] and color_temp[5] == color_temp[6]:
+                color = '#' + color[1] + color[3] + color[5]
+
+        if data.optimize_colors.has_key(color.lower()):
+            color = data.optimize_colors[color.lower()]
+
+        return color
\ No newline at end of file
diff --git a/apps/compress/filters/csstidy_python/output.py b/apps/compress/filters/csstidy_python/output.py
new file mode 100644 (file)
index 0000000..795a0d0
--- /dev/null
@@ -0,0 +1,101 @@
+# CSSTidy - CSS Printer
+#
+# CSS Printer class
+#
+# This file is part of CSSTidy.
+#
+# CSSTidy 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 2 of the License, or
+# (at your option) any later version.
+#
+# CSSTidy 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 CSSTidy if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# @license http://opensource.org/licenses/gpl-license.php GNU Public License
+# @package csstidy
+# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
+
+import data
+
+class CSSPrinter(object):
+    def __init__(self, parser):
+        self.parser = parser
+        self._css = {}
+        self.__renderMethods = {'string': self.__renderString, 'file': self.__renderFile}
+
+#PUBLIC METHODS
+    def prepare(self, css):
+        self._css = css
+
+    def render(self, output="string", *args, **kwargs):
+        return self.__renderMethods[output](*args, **kwargs)
+
+#PRIVATE METHODS
+    def __renderString(self, *args, **kwargs):
+        ##OPTIMIZE##
+        template = self.parser.getSetting('template')
+        ret = ""
+
+        if template == 'highest_compression':
+            top_line_end = ""
+            iner_line_end = ""
+            bottom_line_end = ""
+            indent = ""
+
+        elif template == 'high_compression':
+            top_line_end = "\n"
+            iner_line_end = ""
+            bottom_line_end = "\n"
+            indent = ""
+
+        elif template == 'default':
+            top_line_end = "\n"
+            iner_line_end = "\n"
+            bottom_line_end = "\n\n"
+            indent = ""
+
+        elif template == 'low_compression':
+            top_line_end = "\n"
+            iner_line_end = "\n"
+            bottom_line_end = "\n\n"
+            indent = "    "
+
+        if self.parser.getSetting('timestamp'):
+            ret += '/# CSSTidy ' + self.parser.version + ': ' + datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000") + ' #/' + top_line_end
+
+        for item in self.parser._import:
+            ret += '@import(' + item + ');' + top_line_end
+
+        for item in self.parser._charset:
+            ret += '@charset(' + item + ');' + top_line_end
+
+        for item in self.parser._namespace:
+            ret += '@namespace(' + item + ');' + top_line_end
+
+        for media, css in self._css.iteritems():
+            for selector, cssdata in css.iteritems():
+                ret += selector + '{' + top_line_end
+
+                for item, value in cssdata.iteritems():
+                    ret += indent +  item + ':' + value + ';' + iner_line_end
+
+                ret += '}' + bottom_line_end
+
+        return ret
+
+    def __renderFile(self, filename=None, *args, **kwargs):
+        if filename is None:
+            return self.__renderString()
+
+        try:
+            f = open(filename, "w")
+            f.write(self.__renderString())
+        finally:
+            f.close()
\ No newline at end of file
diff --git a/apps/compress/filters/csstidy_python/tools.py b/apps/compress/filters/csstidy_python/tools.py
new file mode 100644 (file)
index 0000000..e62faef
--- /dev/null
@@ -0,0 +1,109 @@
+
+class SortedDict(dict):
+    """
+    A dictionary that keeps its keys in the order in which they're inserted.
+    """
+    def __init__(self, data=None):
+        if data is None:
+            data = {}
+        super(SortedDict, self).__init__(data)
+        if isinstance(data, dict):
+            self.keyOrder = data.keys()
+        else:
+            self.keyOrder = []
+            for key, value in data:
+                if key not in self.keyOrder:
+                    self.keyOrder.append(key)
+
+    def __deepcopy__(self, memo):
+        from copy import deepcopy
+        return self.__class__([(key, deepcopy(value, memo))
+                               for key, value in self.iteritems()])
+
+    def __setitem__(self, key, value):
+        super(SortedDict, self).__setitem__(key, value)
+        if key not in self.keyOrder:
+            self.keyOrder.append(key)
+
+    def __delitem__(self, key):
+        super(SortedDict, self).__delitem__(key)
+        self.keyOrder.remove(key)
+
+    def __iter__(self):
+        for k in self.keyOrder:
+            yield k
+
+    def pop(self, k, *args):
+        result = super(SortedDict, self).pop(k, *args)
+        try:
+            self.keyOrder.remove(k)
+        except ValueError:
+            # Key wasn't in the dictionary in the first place. No problem.
+            pass
+        return result
+
+    def popitem(self):
+        result = super(SortedDict, self).popitem()
+        self.keyOrder.remove(result[0])
+        return result
+
+    def items(self):
+        return zip(self.keyOrder, self.values())
+
+    def iteritems(self):
+        for key in self.keyOrder:
+            yield key, super(SortedDict, self).__getitem__(key)
+
+    def keys(self):
+        return self.keyOrder[:]
+
+    def iterkeys(self):
+        return iter(self.keyOrder)
+
+    def values(self):
+        return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
+
+    def itervalues(self):
+        for key in self.keyOrder:
+            yield super(SortedDict, self).__getitem__(key)
+
+    def update(self, dict_):
+        for k, v in dict_.items():
+            self.__setitem__(k, v)
+
+    def setdefault(self, key, default):
+        if key not in self.keyOrder:
+            self.keyOrder.append(key)
+        return super(SortedDict, self).setdefault(key, default)
+
+    def value_for_index(self, index):
+        """Returns the value of the item at the given zero-based index."""
+        return self[self.keyOrder[index]]
+
+    def insert(self, index, key, value):
+        """Inserts the key, value pair before the item with the given index."""
+        if key in self.keyOrder:
+            n = self.keyOrder.index(key)
+            del self.keyOrder[n]
+            if n < index:
+                index -= 1
+        self.keyOrder.insert(index, key)
+        super(SortedDict, self).__setitem__(key, value)
+
+    def copy(self):
+        """Returns a copy of this object."""
+        # This way of initializing the copy means it works for subclasses, too.
+        obj = self.__class__(self)
+        obj.keyOrder = self.keyOrder[:]
+        return obj
+
+    def __repr__(self):
+        """
+        Replaces the normal dict.__repr__ with a version that returns the keys
+        in their sorted order.
+        """
+        return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
+
+    def clear(self):
+        super(SortedDict, self).clear()
+        self.keyOrder = []
\ No newline at end of file
diff --git a/apps/compress/filters/jsmin/__init__.py b/apps/compress/filters/jsmin/__init__.py
new file mode 100644 (file)
index 0000000..d226200
--- /dev/null
@@ -0,0 +1,6 @@
+from compress.filters.jsmin.jsmin import jsmin
+from compress.filter_base import FilterBase
+
+class JSMinFilter(FilterBase):
+    def filter_js(self, js):
+        return jsmin(js)
\ No newline at end of file
diff --git a/apps/compress/filters/jsmin/jsmin.py b/apps/compress/filters/jsmin/jsmin.py
new file mode 100644 (file)
index 0000000..4f9d384
--- /dev/null
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+
+# This code is original from jsmin by Douglas Crockford, it was translated to
+# Python by Baruch Even. The original code had the following copyright and
+# license.
+#
+# /* jsmin.c
+#    2007-05-22
+#
+# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# The Software shall be used for Good, not Evil.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# */
+
+from StringIO import StringIO
+
+def jsmin(js):
+    ins = StringIO(js)
+    outs = StringIO()
+    JavascriptMinify().minify(ins, outs)
+    str = outs.getvalue()
+    if len(str) > 0 and str[0] == '\n':
+        str = str[1:]
+    return str
+
+def isAlphanum(c):
+    """return true if the character is a letter, digit, underscore,
+           dollar sign, or non-ASCII character.
+    """
+    return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
+            (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
+
+class UnterminatedComment(Exception):
+    pass
+
+class UnterminatedStringLiteral(Exception):
+    pass
+
+class UnterminatedRegularExpression(Exception):
+    pass
+
+class JavascriptMinify(object):
+
+    def _outA(self):
+        self.outstream.write(self.theA)
+    def _outB(self):
+        self.outstream.write(self.theB)
+
+    def _get(self):
+        """return the next character from stdin. Watch out for lookahead. If
+           the character is a control character, translate it to a space or
+           linefeed.
+        """
+        c = self.theLookahead
+        self.theLookahead = None
+        if c == None:
+            c = self.instream.read(1)
+        if c >= ' ' or c == '\n':
+            return c
+        if c == '': # EOF
+            return '\000'
+        if c == '\r':
+            return '\n'
+        return ' '
+
+    def _peek(self):
+        self.theLookahead = self._get()
+        return self.theLookahead
+
+    def _next(self):
+        """get the next character, excluding comments. peek() is used to see
+           if a '/' is followed by a '/' or '*'.
+        """
+        c = self._get()
+        if c == '/':
+            p = self._peek()
+            if p == '/':
+                c = self._get()
+                while c > '\n':
+                    c = self._get()
+                return c
+            if p == '*':
+                c = self._get()
+                while 1:
+                    c = self._get()
+                    if c == '*':
+                        if self._peek() == '/':
+                            self._get()
+                            return ' '
+                    if c == '\000':
+                        raise UnterminatedComment()
+
+        return c
+
+    def _action(self, action):
+        """do something! What you do is determined by the argument:
+           1   Output A. Copy B to A. Get the next B.
+           2   Copy B to A. Get the next B. (Delete A).
+           3   Get the next B. (Delete B).
+           action treats a string as a single character. Wow!
+           action recognizes a regular expression if it is preceded by ( or , or =.
+        """
+        if action <= 1:
+            self._outA()
+
+        if action <= 2:
+            self.theA = self.theB
+            if self.theA == "'" or self.theA == '"':
+                while 1:
+                    self._outA()
+                    self.theA = self._get()
+                    if self.theA == self.theB:
+                        break
+                    if self.theA <= '\n':
+                        raise UnterminatedStringLiteral()
+                    if self.theA == '\\':
+                        self._outA()
+                        self.theA = self._get()
+
+
+        if action <= 3:
+            self.theB = self._next()
+            if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
+                                     self.theA == '=' or self.theA == ':' or
+                                     self.theA == '[' or self.theA == '?' or
+                                     self.theA == '!' or self.theA == '&' or
+                                     self.theA == '|' or self.theA == ';' or
+                                     self.theA == '{' or self.theA == '}' or
+                                     self.theA == '\n'):
+                self._outA()
+                self._outB()
+                while 1:
+                    self.theA = self._get()
+                    if self.theA == '/':
+                        break
+                    elif self.theA == '\\':
+                        self._outA()
+                        self.theA = self._get()
+                    elif self.theA <= '\n':
+                        raise UnterminatedRegularExpression()
+                    self._outA()
+                self.theB = self._next()
+
+
+    def _jsmin(self):
+        """Copy the input to the output, deleting the characters which are
+           insignificant to JavaScript. Comments will be removed. Tabs will be
+           replaced with spaces. Carriage returns will be replaced with linefeeds.
+           Most spaces and linefeeds will be removed.
+        """
+        self.theA = '\n'
+        self._action(3)
+
+        while self.theA != '\000':
+            if self.theA == ' ':
+                if isAlphanum(self.theB):
+                    self._action(1)
+                else:
+                    self._action(2)
+            elif self.theA == '\n':
+                if self.theB in ['{', '[', '(', '+', '-']:
+                    self._action(1)
+                elif self.theB == ' ':
+                    self._action(3)
+                else:
+                    if isAlphanum(self.theB):
+                        self._action(1)
+                    else:
+                        self._action(2)
+            else:
+                if self.theB == ' ':
+                    if isAlphanum(self.theA):
+                        self._action(1)
+                    else:
+                        self._action(3)
+                elif self.theB == '\n':
+                    if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
+                        self._action(1)
+                    else:
+                        if isAlphanum(self.theA):
+                            self._action(1)
+                        else:
+                            self._action(3)
+                else:
+                    self._action(1)
+
+    def minify(self, instream, outstream):
+        self.instream = instream
+        self.outstream = outstream
+        self.theA = '\n'
+        self.theB = None
+        self.theLookahead = None
+
+        self._jsmin()
+        self.instream.close()
+
+if __name__ == '__main__':
+    import sys
+    jsm = JavascriptMinify()
+    jsm.minify(sys.stdin, sys.stdout)
\ No newline at end of file
diff --git a/apps/compress/filters/yui/__init__.py b/apps/compress/filters/yui/__init__.py
new file mode 100644 (file)
index 0000000..1e2e711
--- /dev/null
@@ -0,0 +1,44 @@
+import subprocess
+
+from django.conf import settings
+
+from compress.filter_base import FilterBase, FilterError
+
+BINARY = getattr(settings, 'COMPRESS_YUI_BINARY', 'java -jar yuicompressor.jar')
+CSS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_CSS_ARGUMENTS', '')
+JS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_JS_ARGUMENTS', '')
+
+class YUICompressorFilter(FilterBase):
+
+    def filter_common(self, content, type_, arguments):
+        command = '%s --type=%s %s' % (BINARY, type_, arguments)
+
+        if self.verbose:
+            command += ' --verbose'
+
+        p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
+        p.stdin.write(content)
+        p.stdin.close()
+
+        filtered_css = p.stdout.read()
+        p.stdout.close()
+
+        err = p.stderr.read()
+        p.stderr.close()
+
+        if p.wait() != 0:
+            if not err:
+                err = 'Unable to apply YUI Compressor filter'
+
+            raise FilterError(err)
+
+        if self.verbose:
+            print err
+
+        return filtered_css
+
+    def filter_js(self, js):
+        return self.filter_common(js, 'js', JS_ARGUMENTS)
+
+    def filter_css(self, css):
+        return self.filter_common(css, 'css', CSS_ARGUMENTS)
\ No newline at end of file
diff --git a/apps/compress/management/__init__.py b/apps/compress/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/management/commands/__init__.py b/apps/compress/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/management/commands/synccompress.py b/apps/compress/management/commands/synccompress.py
new file mode 100644 (file)
index 0000000..6e31d25
--- /dev/null
@@ -0,0 +1,51 @@
+from django.core.management.base import NoArgsCommand
+from optparse import make_option
+
+from django.conf import settings
+
+class Command(NoArgsCommand):
+    option_list = NoArgsCommand.option_list + (
+        make_option('--force', action='store_true', default=False, help='Force update of all files, even if the source files are older than the current compressed file.'),
+        make_option('--verbosity', action='store', dest='verbosity', default='1',
+            type='choice', choices=['0', '1', '2'],
+            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
+    )
+    help = 'Updates and compresses CSS and JavsScript on-demand, without restarting Django'
+    args = ''
+
+    def handle_noargs(self, **options):
+        
+        force = options.get('force', False)
+        verbosity = int(options.get('verbosity', 1))
+
+        from compress.utils import needs_update, filter_css, filter_js
+
+        for name, css in settings.COMPRESS_CSS.items():
+            u, version = needs_update(css['output_filename'], css['source_filenames'])
+
+            if (force or u) or verbosity >= 2:
+                msg = 'CSS Group \'%s\'' % name
+                print msg
+                print len(msg) * '-'
+                print "Version: %s" % version
+
+            if force or u:
+                filter_css(css, verbosity)
+
+            if (force or u) or verbosity >= 2:
+                print
+
+        for name, js in settings.COMPRESS_JS.items():
+            u, version = needs_update(js['output_filename'], js['source_filenames'])
+
+            if (force or u) or verbosity >= 2:
+                msg = 'JavaScript Group \'%s\'' % name
+                print msg
+                print len(msg) * '-'
+                print "Version: %s" % version
+
+            if force or u:
+                filter_js(js, verbosity)
+
+            if (force or u) or verbosity >= 2:
+                print
\ No newline at end of file
diff --git a/apps/compress/models.py b/apps/compress/models.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/signals.py b/apps/compress/signals.py
new file mode 100644 (file)
index 0000000..bd76a76
--- /dev/null
@@ -0,0 +1,4 @@
+from django.dispatch import Signal
+
+css_filtered = Signal()
+js_filtered = Signal()
diff --git a/apps/compress/templates/compress/css.html b/apps/compress/templates/compress/css.html
new file mode 100644 (file)
index 0000000..68ddbac
--- /dev/null
@@ -0,0 +1 @@
+<link href="{{ url }}" rel="stylesheet" type="text/css" media="{{ media|default:"all" }}" />
\ No newline at end of file
diff --git a/apps/compress/templates/compress/css_ie.html b/apps/compress/templates/compress/css_ie.html
new file mode 100644 (file)
index 0000000..80372dc
--- /dev/null
@@ -0,0 +1 @@
+<!--[if {{ condition|default:"IE" }}]><link href="{{ url }}" rel="stylesheet" type="text/css" media="{{ media|default:"all" }}" /><![endif]-->
\ No newline at end of file
diff --git a/apps/compress/templates/compress/js.html b/apps/compress/templates/compress/js.html
new file mode 100644 (file)
index 0000000..bfa2b59
--- /dev/null
@@ -0,0 +1 @@
+<script type="text/javascript" src="{{ url }}"></script>
\ No newline at end of file
diff --git a/apps/compress/templates/compress/js_ie.html b/apps/compress/templates/compress/js_ie.html
new file mode 100644 (file)
index 0000000..8235fe2
--- /dev/null
@@ -0,0 +1 @@
+<!--[if {{ condition|default:"IE" }}]><script type="text/javascript" src="{{ url }}"></script><![endif]-->
\ No newline at end of file
diff --git a/apps/compress/templatetags/__init__.py b/apps/compress/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/compress/templatetags/compressed.py b/apps/compress/templatetags/compressed.py
new file mode 100644 (file)
index 0000000..2a02009
--- /dev/null
@@ -0,0 +1,104 @@
+import os
+
+from django import template
+
+from django.conf import settings as django_settings
+
+from compress.conf import settings
+from compress.utils import media_root, media_url, needs_update, filter_css, filter_js, get_output_filename, get_version
+
+register = template.Library()
+
+def render_common(template_name, obj, filename, version):
+    if settings.COMPRESS:
+        filename = get_output_filename(filename, version)
+
+    context = obj.get('extra_context', {})
+    context['url'] = media_url(filename)
+
+    return template.loader.render_to_string(template_name, context)
+
+def render_css(css, filename, version=None):
+    return render_common(css.get('template_name', 'compress/css.html'), css, filename, version)
+
+def render_js(js, filename, version=None):
+    return render_common(js.get('template_name', 'compress/js.html'), js, filename, version)
+
+class CompressedCSSNode(template.Node):
+    def __init__(self, name):
+        self.name = name
+
+    def render(self, context):
+        css_name = template.Variable(self.name).resolve(context)
+
+        try:
+            css = settings.COMPRESS_CSS[css_name]
+        except KeyError:
+            return '' # fail silently, do not return anything if an invalid group is specified
+
+        if settings.COMPRESS:
+
+            version = None
+
+            if settings.COMPRESS_AUTO:
+                u, version = needs_update(css['output_filename'], css['source_filenames'])
+                if u:
+                    filter_css(css)
+
+            return render_css(css, css['output_filename'], version)
+        else:
+            # output source files
+            r = ''
+            for source_file in css['source_filenames']:
+                r += render_css(css, source_file)
+
+            return r
+
+class CompressedJSNode(template.Node):
+    def __init__(self, name):
+        self.name = name
+
+    def render(self, context):
+        js_name = template.Variable(self.name).resolve(context)
+
+        try:
+            js = settings.COMPRESS_JS[js_name]
+        except KeyError:
+            return '' # fail silently, do not return anything if an invalid group is specified
+
+        if settings.COMPRESS:
+
+            version = None
+
+            if settings.COMPRESS_AUTO:
+                u, version = needs_update(js['output_filename'], js['source_filenames'])
+                if u:
+                    filter_js(js)
+
+            return render_js(js, js['output_filename'], version)
+        else:
+            # output source files
+            r = ''
+            for source_file in js['source_filenames']:
+                r += render_js(js, source_file)
+            return r
+
+#@register.tag
+def compressed_css(parser, token):
+    try:
+        tag_name, name = token.split_contents()
+    except ValueError:
+        raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_CSS setting' % token.split_contents()[0]
+
+    return CompressedCSSNode(name)
+compressed_css = register.tag(compressed_css)
+
+#@register.tag
+def compressed_js(parser, token):
+    try:
+        tag_name, name = token.split_contents()
+    except ValueError:
+        raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_JS setting' % token.split_contents()[0]
+
+    return CompressedJSNode(name)
+compressed_js = register.tag(compressed_js)
diff --git a/apps/compress/utils.py b/apps/compress/utils.py
new file mode 100644 (file)
index 0000000..1e0681f
--- /dev/null
@@ -0,0 +1,130 @@
+import os
+import re
+import tempfile
+
+from django.conf import settings as django_settings
+from django.utils.http import urlquote
+from django.dispatch import dispatcher
+
+from compress.conf import settings
+from compress.signals import css_filtered, js_filtered
+
+def get_filter(compressor_class):
+    """
+    Convert a string version of a function name to the callable object.
+    """
+
+    if not hasattr(compressor_class, '__bases__'):
+
+        try:
+            compressor_class = compressor_class.encode('ascii')
+            mod_name, class_name = get_mod_func(compressor_class)
+            if class_name != '':
+                compressor_class = getattr(__import__(mod_name, {}, {}, ['']), class_name)
+        except (ImportError, AttributeError):
+            raise Exception('Failed to import filter %s' % compressor_class)
+
+    return compressor_class
+
+def get_mod_func(callback):
+    """
+    Converts 'django.views.news.stories.story_detail' to
+    ('django.views.news.stories', 'story_detail')
+    """
+
+    try:
+        dot = callback.rindex('.')
+    except ValueError:
+        return callback, ''
+    return callback[:dot], callback[dot+1:]
+
+def needs_update(output_file, source_files):
+    """
+    Scan the source files for changes and returns True if the output_file needs to be updated.
+    """
+
+    mtime = max_mtime(source_files)
+    version = get_version(mtime)
+
+    compressed_file_full = media_root(get_output_filename(output_file, version))
+
+    if not os.path.exists(compressed_file_full):
+        return True, version
+
+    # Check if the output file is outdated
+    return (os.stat(compressed_file_full).st_mtime < mtime), mtime
+
+def media_root(filename):
+    """
+    Return the full path to ``filename``. ``filename`` is a relative path name in MEDIA_ROOT
+    """
+    return os.path.join(django_settings.MEDIA_ROOT, filename)
+
+def media_url(url):
+    return django_settings.MEDIA_URL + urlquote(url)
+
+def concat(filenames, separator=''):
+    """
+    Concatenate the files from the list of the ``filenames``, ouput separated with ``separator``.
+    """
+    r = ''
+
+    for filename in filenames:
+        fd = open(media_root(filename), 'rb')
+        r += fd.read()
+        r += separator
+        fd.close()
+
+    return r
+
+def max_mtime(files):
+    return int(max([os.stat(media_root(f)).st_mtime for f in files]))
+
+def save_file(filename, contents):
+    fd = open(media_root(filename), 'wb+')
+    fd.write(contents)
+    fd.close()
+
+def get_output_filename(filename, version):
+    if settings.COMPRESS_VERSION and version is not None:
+        return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, get_version(version))
+    else:
+        return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, settings.COMPRESS_VERSION_DEFAULT)
+
+def get_version(version):
+    try:
+        return str(int(version))
+    except ValueError:
+        return str(version)
+
+def remove_files(path, filename, verbosity=0):    
+    regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'\d+'))))
+
+    for f in os.listdir(path):
+        if regex.match(f):
+            if verbosity >= 1:
+                print "Removing outdated file %s" % f
+
+            os.unlink(os.path.join(path, f))
+
+def filter_common(obj, verbosity, filters, attr, separator, signal):
+    output = concat(obj['source_filenames'], separator)
+    filename = get_output_filename(obj['output_filename'], get_version(max_mtime(obj['source_filenames'])))
+
+    if settings.COMPRESS_VERSION:
+        remove_files(os.path.dirname(media_root(filename)), obj['output_filename'], verbosity)
+
+    if verbosity >= 1:
+        print "Saving %s" % filename
+
+    for f in filters:
+        output = getattr(get_filter(f)(verbose=(verbosity >= 2)), attr)(output)
+
+    save_file(filename, output)
+    signal.send(None)
+
+def filter_css(css, verbosity=0):
+    return filter_common(css, verbosity, filters=settings.COMPRESS_CSS_FILTERS, attr='filter_css', separator='', signal=css_filtered)
+
+def filter_js(js, verbosity=0):
+    return filter_common(js, verbosity, filters=settings.COMPRESS_JS_FILTERS, attr='filter_js', separator=';', signal=js_filtered)
diff --git a/apps/newtagging/__init__.py b/apps/newtagging/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/newtagging/admin.py b/apps/newtagging/admin.py
new file mode 100644 (file)
index 0000000..956d2cf
--- /dev/null
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+from django.contrib import admin
+from django import forms
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext_lazy as _
+
+
+class FilteredSelectMultiple(forms.SelectMultiple):
+    """
+    A SelectMultiple with a JavaScript filter interface.
+
+    Note that the resulting JavaScript assumes that the SelectFilter2.js
+    library and its dependencies have been loaded in the HTML page.
+    """
+    def _media(self):
+        from django.conf import settings
+        js = ['js/SelectBox.js' , 'js/SelectFilter2.js']
+        return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
+    media = property(_media)
+    
+    def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
+        self.verbose_name = verbose_name
+        self.is_stacked = is_stacked
+        super(FilteredSelectMultiple, self).__init__(attrs, choices)
+
+    def render(self, name, value, attrs=None, choices=()):
+        from django.conf import settings
+        output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
+        output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
+        # TODO: "id_" is hard-coded here. This should instead use the correct
+        # API to determine the ID dynamically.
+        output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
+            (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
+        return mark_safe(u''.join(output))
+
+
+class TaggableModelForm(forms.ModelForm):
+    tags = forms.MultipleChoiceField(label=_('tags').capitalize(), required=True, widget=FilteredSelectMultiple(_('tags'), False))
+
+    def __init__(self, *args, **kwargs):
+        if 'instance' in kwargs:
+            if 'initial' not in kwargs:
+                kwargs['initial'] = {}
+            kwargs['initial']['tags'] = [tag.id for tag in self.tag_model.objects.get_for_object(kwargs['instance'])]
+        super(TaggableModelForm, self).__init__(*args, **kwargs)
+        self.fields['tags'].choices = [(tag.id, tag.name) for tag in self.tag_model.objects.all()]
+    
+    def save(self, commit):
+        obj = super(TaggableModelForm, self).save()
+        tag_ids = self.cleaned_data['tags']
+        tags = self.tag_model.objects.filter(pk__in=tag_ids)
+        self.tag_model.objects.update_tags(obj, tags)
+        return obj
+
+    def save_m2m(self):
+        # TODO: Shouldn't be needed
+        pass
+
+
+class TaggableModelAdmin(admin.ModelAdmin):
+    form = TaggableModelForm
+    
+    def get_form(self, request, obj=None):
+        form = super(TaggableModelAdmin, self).get_form(request, obj)
+        form.tag_model = self.tag_model
+        return form
+
diff --git a/apps/newtagging/managers.py b/apps/newtagging/managers.py
new file mode 100644 (file)
index 0000000..1dbcb29
--- /dev/null
@@ -0,0 +1,78 @@
+"""
+Custom managers for Django models registered with the tagging
+application.
+"""
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+
+
+class ModelTagManager(models.Manager):
+    """
+    A manager for retrieving tags for a particular model.
+    """
+    def __init__(self, tag_model):
+        super(ModelTagManager, self).__init__()
+        self.tag_model = tag_model
+    
+    def get_query_set(self):
+        content_type = ContentType.objects.get_for_model(self.model)
+        return self.tag_model.objects.filter(
+            items__content_type__pk=content_type.pk).distinct()
+    
+    def related(self, tags, *args, **kwargs):
+        return self.tag_model.objects.related_for_model(tags, self.model, *args, **kwargs)
+    
+    def usage(self, *args, **kwargs):
+        return self.tag_model.objects.usage_for_model(self.model, *args, **kwargs)
+
+
+class ModelTaggedItemManager(models.Manager):
+    """
+    A manager for retrieving model instances based on their tags.
+    """
+    def __init__(self, tag_model):
+        super(ModelTaggedItemManager, self).__init__()
+        self.intermediary_table_model = tag_model.objects.intermediary_table_model
+
+    def related_to(self, obj, queryset=None, num=None):
+        if queryset is None:
+            return self.intermediary_table_model.objects.get_related(obj, self.model, num=num)
+        else:
+            return self.intermediary_table_model.objects.get_related(obj, queryset, num=num)
+
+    def with_all(self, tags, queryset=None):
+        if queryset is None:
+            return self.intermediary_table_model.objects.get_by_model(self.model, tags)
+        else:
+            return self.intermediary_table_model.objects.get_by_model(queryset, tags)
+
+    def with_any(self, tags, queryset=None):
+        if queryset is None:
+            return self.intermediary_table_model.objects.get_union_by_model(self.model, tags)
+        else:
+            return self.intermediary_table_model.objects.get_union_by_model(queryset, tags)
+
+
+class TagDescriptor(object):
+    """
+    A descriptor which provides access to a ``ModelTagManager`` for
+    model classes and simple retrieval, updating and deletion of tags
+    for model instances.
+    """
+    def __init__(self, tag_model):
+        self.tag_model = tag_model
+    
+    def __get__(self, instance, owner):
+        if not instance:
+            tag_manager = ModelTagManager(self.tag_model)
+            tag_manager.model = owner
+            return tag_manager
+        else:
+            return self.tag_model.objects.get_for_object(instance)
+
+    def __set__(self, instance, value):
+        self.tag_model.objects.update_tags(instance, value)
+
+    def __del__(self, instance):
+        self.tag_model.objects.update_tags(instance, [])
+
diff --git a/apps/newtagging/models.py b/apps/newtagging/models.py
new file mode 100644 (file)
index 0000000..0c2b0e1
--- /dev/null
@@ -0,0 +1,510 @@
+"""
+Models and managers for generic tagging.
+"""
+# Python 2.3 compatibility
+if not hasattr(__builtins__, 'set'):
+    from sets import Set as set
+
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.db import connection, models
+from django.utils.translation import ugettext_lazy as _
+from django.db.models.base import ModelBase
+
+qn = connection.ops.quote_name
+
+try:
+    from django.db.models.query import parse_lookup
+except ImportError:
+    parse_lookup = None
+
+
+def get_queryset_and_model(queryset_or_model):
+    """
+    Given a ``QuerySet`` or a ``Model``, returns a two-tuple of
+    (queryset, model).
+
+    If a ``Model`` is given, the ``QuerySet`` returned will be created
+    using its default manager.
+    """
+    try:
+        return queryset_or_model, queryset_or_model.model
+    except AttributeError:
+        return queryset_or_model._default_manager.all(), queryset_or_model
+
+
+############
+# Managers #
+############
+class TagManager(models.Manager):
+    def __init__(self, intermediary_table_model):
+        super(TagManager, self).__init__()
+        self.intermediary_table_model = intermediary_table_model
+    
+    def update_tags(self, obj, tags):
+        """
+        Update tags associated with an object.
+        """
+        content_type = ContentType.objects.get_for_model(obj)
+        current_tags = list(self.filter(items__content_type__pk=content_type.pk,
+                                        items__object_id=obj.pk))
+        updated_tags = self.model.get_tag_list(tags)
+    
+        # Remove tags which no longer apply
+        tags_for_removal = [tag for tag in current_tags \
+                            if tag not in updated_tags]
+        if len(tags_for_removal):
+            self.intermediary_table_model._default_manager.filter(content_type__pk=content_type.pk,
+                                               object_id=obj.pk,
+                                               tag__in=tags_for_removal).delete()
+        # Add new tags
+        for tag in updated_tags:
+            if tag not in current_tags:
+                self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj)
+    
+    def get_for_object(self, obj):
+        """
+        Create a queryset matching all tags associated with the given
+        object.
+        """
+        ctype = ContentType.objects.get_for_model(obj)
+        return self.filter(items__content_type__pk=ctype.pk,
+                           items__object_id=obj.pk)
+    
+    def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None, extra=None):
+        """
+        Perform the custom SQL query for ``usage_for_model`` and
+        ``usage_for_queryset``.
+        """
+        if min_count is not None: counts = True
+
+        model_table = qn(model._meta.db_table)
+        model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column))
+        tag_columns = self._get_tag_columns()
+        
+        if extra is None: extra = {}
+        extra_where = ''
+        if 'where' in extra:
+            extra_where = 'AND ' + ' AND '.join(extra['where'])
+        
+        query = """
+        SELECT DISTINCT %(tag_columns)s%(count_sql)s
+        FROM
+            %(tag)s
+            INNER JOIN %(tagged_item)s
+                ON %(tag)s.id = %(tagged_item)s.tag_id
+            INNER JOIN %(model)s
+                ON %(tagged_item)s.object_id = %(model_pk)s
+            %%s
+        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
+            %%s
+            %(extra_where)s
+        GROUP BY %(tag)s.id, %(tag)s.name
+        %%s
+        ORDER BY %(tag)s.%(ordering)s ASC""" % {
+            'tag': qn(self.model._meta.db_table),
+            'ordering': ', '.join(qn(field) for field in self.model._meta.ordering),
+            'tag_columns': tag_columns,
+            'count_sql': counts and (', COUNT(%s)' % model_pk) or '',
+            'tagged_item': qn(self.intermediary_table_model._meta.db_table),
+            'model': model_table,
+            'model_pk': model_pk,
+            'extra_where': extra_where,
+            'content_type_id': ContentType.objects.get_for_model(model).pk,
+        }
+
+        min_count_sql = ''
+        if min_count is not None:
+            min_count_sql = 'HAVING COUNT(%s) >= %%s' % model_pk
+            params.append(min_count)
+
+        cursor = connection.cursor()
+        cursor.execute(query % (extra_joins, extra_criteria, min_count_sql), params)
+        tags = []
+        for row in cursor.fetchall():
+            t = self.model(*row[:len(self.model._meta.fields)])
+            if counts:
+                t.count = row[len(self.model._meta.fields)]
+            tags.append(t)
+        return tags
+
+    def usage_for_model(self, model, counts=False, min_count=None, filters=None, extra=None):
+        """
+        Obtain a list of tags associated with instances of the given
+        Model class.
+
+        If ``counts`` is True, a ``count`` attribute will be added to
+        each tag, indicating how many times it has been used against
+        the Model class in question.
+
+        If ``min_count`` is given, only tags which have a ``count``
+        greater than or equal to ``min_count`` will be returned.
+        Passing a value for ``min_count`` implies ``counts=True``.
+
+        To limit the tags (and counts, if specified) returned to those
+        used by a subset of the Model's instances, pass a dictionary
+        of field lookups to be applied to the given Model as the
+        ``filters`` argument.
+        """
+        if extra is None: extra = {}
+        if filters is None: filters = {}
+
+        if not parse_lookup:
+            # post-queryset-refactor (hand off to usage_for_queryset)
+            queryset = model._default_manager.filter()
+            for f in filters.items():
+                queryset.query.add_filter(f)
+            usage = self.usage_for_queryset(queryset, counts, min_count, extra)
+        else:
+            # pre-queryset-refactor
+            extra_joins = ''
+            extra_criteria = ''
+            params = []
+            if len(filters) > 0:
+                joins, where, params = parse_lookup(filters.items(), model._meta)
+                extra_joins = ' '.join(['%s %s AS %s ON %s' % (join_type, table, alias, condition)
+                                        for (alias, (table, join_type, condition)) in joins.items()])
+                extra_criteria = 'AND %s' % (' AND '.join(where))
+            usage = self._get_usage(model, counts, min_count, extra_joins, extra_criteria, params, extra)
+
+        return usage
+
+    def usage_for_queryset(self, queryset, counts=False, min_count=None, extra=None):
+        """
+        Obtain a list of tags associated with instances of a model
+        contained in the given queryset.
+
+        If ``counts`` is True, a ``count`` attribute will be added to
+        each tag, indicating how many times it has been used against
+        the Model class in question.
+
+        If ``min_count`` is given, only tags which have a ``count``
+        greater than or equal to ``min_count`` will be returned.
+        Passing a value for ``min_count`` implies ``counts=True``.
+        """
+        if parse_lookup:
+            raise AttributeError("'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django.")
+
+        extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:])
+        where, params = queryset.query.where.as_sql()
+        if where:
+            extra_criteria = 'AND %s' % where
+        else:
+            extra_criteria = ''
+        return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params, extra)
+
+    def related_for_model(self, tags, model, counts=False, min_count=None, extra=None):
+        """
+        Obtain a list of tags related to a given list of tags - that
+        is, other tags used by items which have all the given tags.
+
+        If ``counts`` is True, a ``count`` attribute will be added to
+        each tag, indicating the number of items which have it in
+        addition to the given list of tags.
+
+        If ``min_count`` is given, only tags which have a ``count``
+        greater than or equal to ``min_count`` will be returned.
+        Passing a value for ``min_count`` implies ``counts=True``.
+        """
+        if min_count is not None: counts = True
+        tags = self.model.get_tag_list(tags)
+        tag_count = len(tags)
+        tagged_item_table = qn(self.intermediary_table_model._meta.db_table)
+        tag_columns = self._get_tag_columns()
+        
+        if extra is None: extra = {}
+        extra_where = ''
+        if 'where' in extra:
+            extra_where = 'AND ' + ' AND '.join(extra['where'])
+        
+        query = """
+        SELECT %(tag_columns)s%(count_sql)s
+        FROM %(tagged_item)s INNER JOIN %(tag)s ON %(tagged_item)s.tag_id = %(tag)s.id
+        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
+          AND %(tagged_item)s.object_id IN
+          (
+              SELECT %(tagged_item)s.object_id
+              FROM %(tagged_item)s, %(tag)s
+              WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
+                AND %(tag)s.id = %(tagged_item)s.tag_id
+                AND %(tag)s.id IN (%(tag_id_placeholders)s)
+              GROUP BY %(tagged_item)s.object_id
+              HAVING COUNT(%(tagged_item)s.object_id) = %(tag_count)s
+          )
+          AND %(tag)s.id NOT IN (%(tag_id_placeholders)s)
+          %(extra_where)s
+        GROUP BY %(tag)s.id, %(tag)s.name
+        %(min_count_sql)s
+        ORDER BY %(tag)s.%(ordering)s ASC""" % {
+            'tag': qn(self.model._meta.db_table),
+            'ordering': ', '.join(qn(field) for field in self.model._meta.ordering),
+            'tag_columns': tag_columns,
+            'count_sql': counts and ', COUNT(%s.object_id)' % tagged_item_table or '',
+            'tagged_item': tagged_item_table,
+            'content_type_id': ContentType.objects.get_for_model(model).pk,
+            'tag_id_placeholders': ','.join(['%s'] * tag_count),
+            'extra_where': extra_where,
+            'tag_count': tag_count,
+            'min_count_sql': min_count is not None and ('HAVING COUNT(%s.object_id) >= %%s' % tagged_item_table) or '',
+        }
+
+        params = [tag.pk for tag in tags] * 2
+        if min_count is not None:
+            params.append(min_count)
+
+        cursor = connection.cursor()
+        cursor.execute(query, params)
+        related = []
+        for row in cursor.fetchall():
+            tag = self.model(*row[:len(self.model._meta.fields)])
+            if counts is True:
+                tag.count = row[len(self.model._meta.fields)]
+            related.append(tag)
+        return related
+
+    def _get_tag_columns(self):
+        tag_table = qn(self.model._meta.db_table)
+        return ', '.join('%s.%s' % (tag_table, qn(field.column)) for field in self.model._meta.fields)
+
+
+class TaggedItemManager(models.Manager):
+    """
+    FIXME There's currently no way to get the ``GROUP BY`` and ``HAVING``
+          SQL clauses required by many of this manager's methods into
+          Django's ORM.
+
+          For now, we manually execute a query to retrieve the PKs of
+          objects we're interested in, then use the ORM's ``__in``
+          lookup to return a ``QuerySet``.
+
+          Once the queryset-refactor branch lands in trunk, this can be
+          tidied up significantly.
+    """
+    def __init__(self, tag_model):
+        super(TaggedItemManager, self).__init__()
+        self.tag_model = tag_model
+    
+    def get_by_model(self, queryset_or_model, tags):
+        """
+        Create a ``QuerySet`` containing instances of the specified
+        model associated with a given tag or list of tags.
+        """
+        tags = self.tag_model.get_tag_list(tags)
+        tag_count = len(tags)
+        if tag_count == 0:
+            # No existing tags were given
+            queryset, model = get_queryset_and_model(queryset_or_model)
+            return model._default_manager.none()
+        elif tag_count == 1:
+            # Optimisation for single tag - fall through to the simpler
+            # query below.
+            tag = tags[0]
+        else:
+            return self.get_intersection_by_model(queryset_or_model, tags)
+
+        queryset, model = get_queryset_and_model(queryset_or_model)
+        content_type = ContentType.objects.get_for_model(model)
+        opts = self.model._meta
+        tagged_item_table = qn(opts.db_table)
+        return queryset.extra(
+            tables=[opts.db_table],
+            where=[
+                '%s.content_type_id = %%s' % tagged_item_table,
+                '%s.tag_id = %%s' % tagged_item_table,
+                '%s.%s = %s.object_id' % (qn(model._meta.db_table),
+                                          qn(model._meta.pk.column),
+                                          tagged_item_table)
+            ],
+            params=[content_type.pk, tag.pk],
+        )
+
+    def get_intersection_by_model(self, queryset_or_model, tags):
+        """
+        Create a ``QuerySet`` containing instances of the specified
+        model associated with *all* of the given list of tags.
+        """
+        tags = self.tag_model.get_tag_list(tags)
+        tag_count = len(tags)
+        queryset, model = get_queryset_and_model(queryset_or_model)
+
+        if not tag_count:
+            return model._default_manager.none()
+
+        model_table = qn(model._meta.db_table)
+        # This query selects the ids of all objects which have all the
+        # given tags.
+        query = """
+        SELECT %(model_pk)s
+        FROM %(model)s, %(tagged_item)s
+        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
+          AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
+          AND %(model_pk)s = %(tagged_item)s.object_id
+        GROUP BY %(model_pk)s
+        HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
+            'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
+            'model': model_table,
+            'tagged_item': qn(self.model._meta.db_table),
+            'content_type_id': ContentType.objects.get_for_model(model).pk,
+            'tag_id_placeholders': ','.join(['%s'] * tag_count),
+            'tag_count': tag_count,
+        }
+
+        cursor = connection.cursor()
+        cursor.execute(query, [tag.pk for tag in tags])
+        object_ids = [row[0] for row in cursor.fetchall()]
+        if len(object_ids) > 0:
+            return queryset.filter(pk__in=object_ids)
+        else:
+            return model._default_manager.none()
+
+    def get_union_by_model(self, queryset_or_model, tags):
+        """
+        Create a ``QuerySet`` containing instances of the specified
+        model associated with *any* of the given list of tags.
+        """
+        tags = self.tag_model.get_tag_list(tags)
+        tag_count = len(tags)
+        queryset, model = get_queryset_and_model(queryset_or_model)
+
+        if not tag_count:
+            return model._default_manager.none()
+
+        model_table = qn(model._meta.db_table)
+        # This query selects the ids of all objects which have any of
+        # the given tags.
+        query = """
+        SELECT %(model_pk)s
+        FROM %(model)s, %(tagged_item)s
+        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
+          AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
+          AND %(model_pk)s = %(tagged_item)s.object_id
+        GROUP BY %(model_pk)s""" % {
+            'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
+            'model': model_table,
+            'tagged_item': qn(self.model._meta.db_table),
+            'content_type_id': ContentType.objects.get_for_model(model).pk,
+            'tag_id_placeholders': ','.join(['%s'] * tag_count),
+        }
+
+        cursor = connection.cursor()
+        cursor.execute(query, [tag.pk for tag in tags])
+        object_ids = [row[0] for row in cursor.fetchall()]
+        if len(object_ids) > 0:
+            return queryset.filter(pk__in=object_ids)
+        else:
+            return model._default_manager.none()
+
+    def get_related(self, obj, queryset_or_model, num=None):
+        """
+        Retrieve a list of instances of the specified model which share
+        tags with the model instance ``obj``, ordered by the number of
+        shared tags in descending order.
+
+        If ``num`` is given, a maximum of ``num`` instances will be
+        returned.
+        """
+        queryset, model = get_queryset_and_model(queryset_or_model)
+        model_table = qn(model._meta.db_table)
+        content_type = ContentType.objects.get_for_model(obj)
+        related_content_type = ContentType.objects.get_for_model(model)
+        query = """
+        SELECT %(model_pk)s, COUNT(related_tagged_item.object_id) AS %(count)s
+        FROM %(model)s, %(tagged_item)s, %(tag)s, %(tagged_item)s related_tagged_item
+        WHERE %(tagged_item)s.object_id = %%s
+          AND %(tagged_item)s.content_type_id = %(content_type_id)s
+          AND %(tag)s.id = %(tagged_item)s.tag_id
+          AND related_tagged_item.content_type_id = %(related_content_type_id)s
+          AND related_tagged_item.tag_id = %(tagged_item)s.tag_id
+          AND %(model_pk)s = related_tagged_item.object_id"""
+        if content_type.pk == related_content_type.pk:
+            # Exclude the given instance itself if determining related
+            # instances for the same model.
+            query += """
+          AND related_tagged_item.object_id != %(tagged_item)s.object_id"""
+        query += """
+        GROUP BY %(model_pk)s
+        ORDER BY %(count)s DESC
+        %(limit_offset)s"""
+        query = query % {
+            'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
+            'count': qn('count'),
+            'model': model_table,
+            'tagged_item': qn(self.model._meta.db_table),
+            'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table),
+            'content_type_id': content_type.pk,
+            'related_content_type_id': related_content_type.pk,
+            'limit_offset': num is not None and connection.ops.limit_offset_sql(num) or '',
+        }
+
+        cursor = connection.cursor()
+        cursor.execute(query, [obj.pk])
+        object_ids = [row[0] for row in cursor.fetchall()]
+        if len(object_ids) > 0:
+            # Use in_bulk here instead of an id__in lookup, because id__in would
+            # clobber the ordering.
+            object_dict = queryset.in_bulk(object_ids)
+            return [object_dict[object_id] for object_id in object_ids \
+                    if object_id in object_dict]
+        else:
+            return []
+
+
+##########
+# Models #
+##########
+def create_intermediary_table_model(model):
+    """Create an intermediary table model for the specific tag model"""
+    name = model.__name__ + 'Relation'
+     
+    class Meta:
+        db_table = '%s_relation' % model._meta.db_table
+        unique_together = (('tag', 'content_type', 'object_id'),)
+
+    def obj_unicode(self):
+        return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
+        
+    # Set up a dictionary to simulate declarations within a class    
+    attrs = {
+        '__module__': model.__module__,
+        'Meta': Meta,
+        'tag': models.ForeignKey(model, verbose_name=_('tag'), related_name='items'),
+        'content_type': models.ForeignKey(ContentType, verbose_name=_('content type')),
+        'object_id': models.PositiveIntegerField(_('object id'), db_index=True),
+        'content_object': generic.GenericForeignKey('content_type', 'object_id'),
+        '__unicode__': obj_unicode,
+    }
+
+    return type(name, (models.Model,), attrs)
+
+
+class TagMeta(ModelBase):
+    "Metaclass for tag models (models inheriting from TagBase)."
+    def __new__(cls, name, bases, attrs):
+        model = super(TagMeta, cls).__new__(cls, name, bases, attrs)
+        if not model._meta.abstract:
+            # Create an intermediary table and register custom managers for concrete models
+            model.intermediary_table_model = create_intermediary_table_model(model)
+            TagManager(model.intermediary_table_model).contribute_to_class(model, 'objects')
+            TaggedItemManager(model).contribute_to_class(model.intermediary_table_model, 'objects')
+        return model
+
+
+class TagBase(models.Model):
+    """Abstract class to be inherited by model classes."""
+    __metaclass__ = TagMeta
+    
+    class Meta:
+        abstract = True
+    
+    @staticmethod
+    def get_tag_list(tag_list):
+        """
+        Utility function for accepting tag input in a flexible manner.
+        
+        You should probably override this method in your subclass.
+        """
+        if isinstance(tag_list, TagBase):
+            return [tag_list]
+        else:
+            return tag_list
+
diff --git a/apps/newtagging/views.py b/apps/newtagging/views.py
new file mode 100644 (file)
index 0000000..150a084
--- /dev/null
@@ -0,0 +1,47 @@
+"""
+Tagging related views.
+"""
+from django.http import Http404
+from django.utils.translation import ugettext as _
+from django.views.generic.list_detail import object_list
+
+
+def tagged_object_list(request, queryset_or_model=None, tag_model=None, tags=None,
+        related_tags=False, related_tag_counts=True, **kwargs):
+    """
+    A thin wrapper around
+    ``django.views.generic.list_detail.object_list`` which creates a
+    ``QuerySet`` containing instances of the given queryset or model
+    tagged with the given tag.
+
+    In addition to the context variables set up by ``object_list``, a
+    ``tag`` context variable will contain the ``Tag`` instance for the
+    tag.
+
+    If ``related_tags`` is ``True``, a ``related_tags`` context variable
+    will contain tags related to the given tag for the given model.
+    Additionally, if ``related_tag_counts`` is ``True``, each related
+    tag will have a ``count`` attribute indicating the number of items
+    which have it in addition to the given tag.
+    """
+    # Check attributes
+    if queryset_or_model is None:
+        raise AttributeError(_('tagged_object_list must be called with a queryset or a model.'))
+    if tag_model is None:
+        raise AttributeError(_('tagged_object_list must be called with a tag model.'))
+    if tags is None:
+        raise AttributeError(_('tagged_object_list must be called with a tag.'))
+
+    tag_instances = tag_model.get_tag_list(tags)
+    if tag_instances is None:
+        raise Http404(_('No tags found matching "%s".') % tags)
+    queryset = tag_model.intermediary_table_model.objects.get_intersection_by_model(queryset_or_model, tag_instances)
+    if not kwargs.has_key('extra_context'):
+        kwargs['extra_context'] = {}
+    kwargs['extra_context']['tags'] = tag_instances
+    if related_tags:
+        kwargs['extra_context']['related_tags'] = \
+            tag_model.objects.related_for_model(tag_instances, queryset_or_model,
+                                          counts=related_tag_counts)
+    return object_list(request, queryset, **kwargs)
+
diff --git a/apps/pagination/__init__.py b/apps/pagination/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/apps/pagination/middleware.py b/apps/pagination/middleware.py
new file mode 100644 (file)
index 0000000..0bab767
--- /dev/null
@@ -0,0 +1,10 @@
+class PaginationMiddleware(object):
+    """
+    Inserts a variable representing the current page onto the request object if
+    it exists in either **GET** or **POST** portions of the request.
+    """
+    def process_request(self, request):
+        try:
+            request.page = int(request.REQUEST['page'])
+        except (KeyError, ValueError):
+            request.page = 1
\ No newline at end of file
diff --git a/apps/pagination/models.py b/apps/pagination/models.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/apps/pagination/templates/pagination/pagination.html b/apps/pagination/templates/pagination/pagination.html
new file mode 100644 (file)
index 0000000..3799314
--- /dev/null
@@ -0,0 +1,25 @@
+{% if is_paginated %}
+<div class="pagination">
+    {% if page_obj.has_previous %}
+        <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">&lsaquo;&lsaquo; previous</a>
+    {% else %}
+        <span class="disabled prev">&lsaquo;&lsaquo; previous</span>
+    {% endif %}
+    {% for page in pages %}
+        {% if page %}
+            {% ifequal page page_obj.number %}
+                <span class="current page">{{ page }}</span>
+            {% else %}
+                <a href="?page={{ page }}{{ getvars }}" class="page">{{ page }}</a>
+            {% endifequal %}
+        {% else %}
+            ...
+        {% endif %}
+    {% endfor %}
+    {% if page_obj.has_next %}
+        <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="next">next &rsaquo;&rsaquo;</a>
+    {% else %}
+        <span class="disabled next">next &rsaquo;&rsaquo;</span>
+    {% endif %}
+</div>
+{% endif %}
diff --git a/apps/pagination/templatetags/__init__.py b/apps/pagination/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/apps/pagination/templatetags/pagination_tags.py b/apps/pagination/templatetags/pagination_tags.py
new file mode 100644 (file)
index 0000000..55e5392
--- /dev/null
@@ -0,0 +1,200 @@
+try:
+    set
+except NameError:
+    from sets import Set as set
+from django import template
+from django.db.models.query import QuerySet
+from django.core.paginator import Paginator, QuerySetPaginator, InvalidPage
+
+register = template.Library()
+
+DEFAULT_PAGINATION = 20
+DEFAULT_WINDOW = 4
+DEFAULT_ORPHANS = 0
+
+def do_autopaginate(parser, token):
+    """
+    Splits the arguments to the autopaginate tag and formats them correctly.
+    """
+    split = token.split_contents()
+    if len(split) == 2:
+        return AutoPaginateNode(split[1])
+    elif len(split) == 3:
+        try:
+            paginate_by = int(split[2])
+        except ValueError:
+            raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
+        return AutoPaginateNode(split[1], paginate_by=paginate_by)
+    elif len(split) == 4:
+        try:
+            paginate_by = int(split[2])
+        except ValueError:
+            raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
+        try:
+            orphans = int(split[3])
+        except ValueError:
+            raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])           
+        return AutoPaginateNode(split[1], paginate_by=paginate_by, orphans=orphans)
+    else:
+        raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
+
+class AutoPaginateNode(template.Node):
+    """
+    Emits the required objects to allow for Digg-style pagination.
+    
+    First, it looks in the current context for the variable specified.  This
+    should be either a QuerySet or a list.
+    
+    1. If it is a QuerySet, this ``AutoPaginateNode`` will emit a 
+       ``QuerySetPaginator`` and the current page object into the context names
+       ``paginator`` and ``page_obj``, respectively.
+    
+    2. If it is a list, this ``AutoPaginateNode`` will emit a simple
+       ``Paginator`` and the current page object into the context names 
+       ``paginator`` and ``page_obj``, respectively.
+    
+    It will then replace the variable specified with only the objects for the
+    current page.
+    
+    .. note::
+        
+        It is recommended to use *{% paginate %}* after using the autopaginate
+        tag.  If you choose not to use *{% paginate %}*, make sure to display the
+        list of availabale pages, or else the application may seem to be buggy.
+    """
+    def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS):
+        self.queryset_var = template.Variable(queryset_var)
+        self.paginate_by = paginate_by
+        self.orphans = orphans
+
+    def render(self, context):
+        key = self.queryset_var.var
+        value = self.queryset_var.resolve(context)
+        if issubclass(value.__class__, QuerySet):
+            model = value.model
+            paginator_class = QuerySetPaginator
+        else:
+            value = list(value)
+            try:
+                model = value[0].__class__
+            except IndexError:
+                return u''
+            paginator_class = Paginator
+        paginator = paginator_class(value, self.paginate_by, self.orphans)
+        try:
+            page_obj = paginator.page(context['request'].page)
+        except InvalidPage:
+            context[key] = []
+            context['invalid_page'] = True
+            return u''
+        context[key] = page_obj.object_list
+        context['paginator'] = paginator
+        context['page_obj'] = page_obj
+        return u''
+
+def paginate(context, window=DEFAULT_WINDOW):
+    """
+    Renders the ``pagination/pagination.html`` template, resulting in a
+    Digg-like display of the available pages, given the current page.  If there
+    are too many pages to be displayed before and after the current page, then
+    elipses will be used to indicate the undisplayed gap between page numbers.
+    
+    Requires one argument, ``context``, which should be a dictionary-like data
+    structure and must contain the following keys:
+    
+    ``paginator``
+        A ``Paginator`` or ``QuerySetPaginator`` object.
+    
+    ``page_obj``
+        This should be the result of calling the page method on the 
+        aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
+        the current page.
+    
+    This same ``context`` dictionary-like data structure may also include:
+    
+    ``getvars``
+        A dictionary of all of the **GET** parameters in the current request.
+        This is useful to maintain certain types of state, even when requesting
+        a different page.
+        """
+    try:
+        paginator = context['paginator']
+        page_obj = context['page_obj']
+        page_range = paginator.page_range
+        # First and last are simply the first *n* pages and the last *n* pages,
+        # where *n* is the current window size.
+        first = set(page_range[:window])
+        last = set(page_range[-window:])
+        # Now we look around our current page, making sure that we don't wrap
+        # around.
+        current_start = page_obj.number-1-window
+        if current_start < 0:
+            current_start = 0
+        current_end = page_obj.number-1+window
+        if current_end < 0:
+            current_end = 0
+        current = set(page_range[current_start:current_end])
+        pages = []
+        # If there's no overlap between the first set of pages and the current
+        # set of pages, then there's a possible need for elusion.
+        if len(first.intersection(current)) == 0:
+            first_list = sorted(list(first))
+            second_list = sorted(list(current))
+            pages.extend(first_list)
+            diff = second_list[0] - first_list[-1]
+            # If there is a gap of two, between the last page of the first
+            # set and the first page of the current set, then we're missing a
+            # page.
+            if diff == 2:
+                pages.append(second_list[0] - 1)
+            # If the difference is just one, then there's nothing to be done,
+            # as the pages need no elusion and are correct.
+            elif diff == 1:
+                pass
+            # Otherwise, there's a bigger gap which needs to be signaled for
+            # elusion, by pushing a None value to the page list.
+            else:
+                pages.append(None)
+            pages.extend(second_list)
+        else:
+            pages.extend(sorted(list(first.union(current))))
+        # If there's no overlap between the current set of pages and the last
+        # set of pages, then there's a possible need for elusion.
+        if len(current.intersection(last)) == 0:
+            second_list = sorted(list(last))
+            diff = second_list[0] - pages[-1]
+            # If there is a gap of two, between the last page of the current
+            # set and the first page of the last set, then we're missing a 
+            # page.
+            if diff == 2:
+                pages.append(second_list[0] - 1)
+            # If the difference is just one, then there's nothing to be done,
+            # as the pages need no elusion and are correct.
+            elif diff == 1:
+                pass
+            # Otherwise, there's a bigger gap which needs to be signaled for
+            # elusion, by pushing a None value to the page list.
+            else:
+                pages.append(None)
+            pages.extend(second_list)
+        else:
+            pages.extend(sorted(list(last.difference(current))))
+        to_return = {
+            'pages': pages,
+            'page_obj': page_obj,
+            'paginator': paginator,
+            'is_paginated': paginator.count > paginator.per_page,
+        }
+        if 'request' in context:
+            getvars = context['request'].GET.copy()
+            if 'page' in getvars:
+                del getvars['page']
+            if len(getvars.keys()) > 0:
+                to_return['getvars'] = "&%s" % getvars.urlencode()
+            else:
+                to_return['getvars'] = ''
+        return to_return
+    except KeyError:
+        return {}
+register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
+register.tag('autopaginate', do_autopaginate)
\ No newline at end of file
diff --git a/apps/pagination/tests.py b/apps/pagination/tests.py
new file mode 100644 (file)
index 0000000..837e55c
--- /dev/null
@@ -0,0 +1,52 @@
+"""
+>>> from django.core.paginator import Paginator
+>>> from pagination.templatetags.pagination_tags import paginate
+>>> from django.template import Template, Context
+
+>>> p = Paginator(range(15), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, 5, 6, 7, 8]
+
+>>> p = Paginator(range(17), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+>>> p = Paginator(range(19), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 7, 8, 9, 10]
+
+>>> p = Paginator(range(21), 2)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 8, 9, 10, 11]
+
+# Testing orphans
+>>> p = Paginator(range(5), 2, 1)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2]
+
+>>> p = Paginator(range(21), 2, 1)
+>>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
+[1, 2, 3, 4, None, 7, 8, 9, 10]
+
+>>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}")
+
+# WARNING: Please, please nobody read this portion of the code!
+>>> class GetProxy(object):
+...     def __iter__(self): yield self.__dict__.__iter__
+...     def copy(self): return self
+...     def urlencode(self): return u''
+...     def keys(self): return []
+>>> class RequestProxy(object):
+...     page = 1
+...     GET = GetProxy()
+>>>
+# ENDWARNING
+
+>>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
+u'\\n<div class="pagination">...
+>>>
+>>> t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
+>>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
+u'\\n<div class="pagination">...
+>>>
+"""
\ No newline at end of file
diff --git a/catalogue/__init__.py b/catalogue/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/catalogue/admin.py b/catalogue/admin.py
deleted file mode 100644 (file)
index 2f5d48b..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.contrib import admin
-
-from newtagging.admin import TaggableModelAdmin
-from catalogue.models import Tag, Book, Fragment
-
-
-class TagAdmin(admin.ModelAdmin):
-    list_display = ('name', 'slug', 'sort_key', 'category', 'has_description',)
-    list_filter = ('category',)
-    search_fields = ('name',)
-    ordering = ('name',)
-
-    prepopulated_fields = {'slug': ('name',), 'sort_key': ('name',),}
-    radio_fields = {'category': admin.HORIZONTAL}
-
-
-class BookAdmin(TaggableModelAdmin):
-    tag_model = Tag
-    
-    list_display = ('title', 'slug', 'has_pdf_file', 'has_odt_file', 'has_html_file', 'has_description',)
-    search_fields = ('title',)
-    ordering = ('title',)
-
-    prepopulated_fields = {'slug': ('title',)}
-
-
-class FragmentAdmin(TaggableModelAdmin):
-    tag_model = Tag
-    
-    list_display = ('book', 'anchor',)
-    ordering = ('book', 'anchor',)
-
-
-admin.site.register(Tag, TagAdmin)
-admin.site.register(Book, BookAdmin)
-admin.site.register(Fragment, FragmentAdmin)
-
diff --git a/catalogue/fields.py b/catalogue/fields.py
deleted file mode 100644 (file)
index e1a356e..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-from django import forms
-from django.forms.widgets import flatatt
-from django.forms.util import smart_unicode
-from django.utils.html import escape
-from django.utils.safestring import mark_safe
-from django.utils.simplejson import dumps
-
-
-class JQueryAutoCompleteWidget(forms.TextInput):
-    def __init__(self, source, options=None, *args, **kwargs):
-        self.source = source
-        self.options = None
-        if options:
-            self.options = dumps(options)
-        super(JQueryAutoCompleteWidget, self).__init__(*args, **kwargs)
-    
-    def render_js(self, field_id):
-        source = "'%s'" % escape(self.source)
-        options = ''
-        if self.options:
-            options += ', %s' % self.options
-        
-        return u'$(\'#%s\').autocomplete(%s%s);' % (field_id, source, options)
-    
-    def render(self, name, value=None, attrs=None):
-        final_attrs = self.build_attrs(attrs, name=name)
-        if value:
-            final_attrs['value'] = smart_unicode(value)
-        
-        if not self.attrs.has_key('id'):
-            final_attrs['id'] = 'id_%s' % name
-        
-        html = u'''<input type="text" %(attrs)s/>
-            <script type="text/javascript"><!--//
-            %(js)s//--></script>
-            ''' % {
-                'attrs' : flatatt(final_attrs),
-                'js' : self.render_js(final_attrs['id']),
-            }
-        
-        return mark_safe(html)
-
-
-class JQueryAutoCompleteField(forms.CharField):
-    def __init__(self, source, options=None, *args, **kwargs):
-        if 'widget' not in kwargs:
-            kwargs['widget'] = JQueryAutoCompleteWidget(source, options)
-        
-        super(JQueryAutoCompleteField, self).__init__(*args, **kwargs)
-
diff --git a/catalogue/forms.py b/catalogue/forms.py
deleted file mode 100644 (file)
index 279ec71..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-from django import forms
-from slughifi import slughifi
-
-from catalogue.models import Tag
-from catalogue.fields import JQueryAutoCompleteField
-
-
-class SearchForm(forms.Form):
-    q = JQueryAutoCompleteField('/katalog/tags/', {'minChars': 2, 'selectFirst': True, 'cacheLength': 50})
-    tags = forms.CharField(widget=forms.HiddenInput, required=False)
-    
-    def __init__(self, *args, **kwargs):
-        tags = kwargs.pop('tags', [])
-        super(SearchForm, self).__init__(*args, **kwargs)
-        self.fields['q'].widget.attrs['title'] = u'tytuł utworu, motyw lub kategoria'
-        self.fields['tags'].initial = '/'.join(tag.slug for tag in Tag.get_tag_list(tags))
-
-
-class UserSetsForm(forms.Form):
-    def __init__(self, book, user, *args, **kwargs):
-        super(UserSetsForm, self).__init__(*args, **kwargs)
-        self.fields['set_ids'] = forms.ChoiceField(
-            choices=[(tag.id, tag.name) for tag in Tag.objects.filter(category='set', user=user)],
-        )
-
-
-class ObjectSetsForm(forms.Form):
-    def __init__(self, obj, user, *args, **kwargs):        
-        super(ObjectSetsForm, self).__init__(*args, **kwargs)
-        self.fields['set_ids'] = forms.MultipleChoiceField(
-            label=u'Półki',
-            required=False,
-            choices=[(tag.id, tag.name) for tag in Tag.objects.filter(category='set', user=user)],
-            initial=[tag.id for tag in obj.tags.filter(category='set', user=user)],
-            widget=forms.CheckboxSelectMultiple
-        )
-        
-
-class NewSetForm(forms.Form):
-    name = forms.CharField(max_length=50, required=True)
-        
-    def save(self, user, commit=True):
-        name = self.cleaned_data['name']
-        new_set = Tag(name=name, slug=slughifi(name), sort_key=slughifi(name),
-            category='set', user=user)
-        
-        new_set.save()
-        return new_set
-
diff --git a/catalogue/management/__init__.py b/catalogue/management/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/catalogue/management/commands/__init__.py b/catalogue/management/commands/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/catalogue/management/commands/importbooks.py b/catalogue/management/commands/importbooks.py
deleted file mode 100644 (file)
index 22dd237..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-import os
-
-from django.core.management.base import BaseCommand
-from django.core.management.color import color_style
-from optparse import make_option
-
-from catalogue.models import Book
-
-
-class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('--verbosity', action='store', dest='verbosity', default='1',
-            type='choice', choices=['0', '1', '2'],
-            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
-    )
-    help = 'Imports books from the specified directories.'
-    args = 'directory [directory ...]'
-
-    def handle(self, *directories, **options):
-        from django.db import transaction
-
-        self.style = color_style()
-
-        verbosity = int(options.get('verbosity', 1))
-        show_traceback = options.get('traceback', False)
-
-        # Start transaction management.
-        transaction.commit_unless_managed()
-        transaction.enter_transaction_management()
-        transaction.managed(True)
-
-        for dir_name in directories:
-            if not os.path.isdir(dir_name):
-                print self.style.ERROR("Skipping '%s': not a directory." % dir_name)
-            else:
-                for file_name in os.listdir(dir_name):
-                    file_path = os.path.join(dir_name, file_name)
-                    if not os.path.splitext(file_name)[1] == '.xml':
-                        print self.style.NOTICE("Skipping '%s': not an XML file." % file_path)
-                        continue
-                    if verbosity > 0:
-                        print "Parsing '%s'" % file_path
-                    
-                    Book.from_xml_file(file_path)
-        
-        transaction.commit()
-        transaction.leave_transaction_management()
-
diff --git a/catalogue/models.py b/catalogue/models.py
deleted file mode 100644 (file)
index f3f6bc2..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.db import models
-from django.db.models import permalink, Q
-from django.utils.translation import ugettext_lazy as _
-from django.contrib.auth.models import User
-from django.core.files import File
-from django.template.loader import render_to_string
-from django.utils.safestring import mark_safe
-
-from newtagging.models import TagBase
-from newtagging import managers
-
-from librarian import html, dcparser
-
-
-TAG_CATEGORIES = (
-    ('author', _('author')),
-    ('epoch', _('epoch')),
-    ('kind', _('kind')),
-    ('genre', _('genre')),
-    ('theme', _('theme')),
-    ('set', _('set')),
-)
-
-
-class TagSubcategoryManager(models.Manager):
-    def __init__(self, subcategory):
-        super(TagSubcategoryManager, self).__init__()
-        self.subcategory = subcategory
-        
-    def get_query_set(self):
-        return super(TagSubcategoryManager, self).get_query_set().filter(category=self.subcategory)
-
-
-class Tag(TagBase):
-    name = models.CharField(_('name'), max_length=50, unique=True, db_index=True)
-    slug = models.SlugField(_('slug'), unique=True, db_index=True)
-    sort_key = models.SlugField(_('sort key'), db_index=True)
-    category = models.CharField(_('category'), max_length=50, blank=False, null=False, 
-        db_index=True, choices=TAG_CATEGORIES)
-    description = models.TextField(blank=True)
-    
-    user = models.ForeignKey(User, blank=True, null=True)
-    
-    def has_description(self):
-        return len(self.description) > 0
-    has_description.short_description = _('description')
-    has_description.boolean = True
-
-    @permalink
-    def get_absolute_url(self):
-        return ('catalogue.views.tagged_object_list', [self.slug])
-    
-    class Meta:
-        ordering = ('sort_key',)
-        verbose_name = _('tag')
-        verbose_name_plural = _('tags')
-    
-    def __unicode__(self):
-        return self.name
-
-    @staticmethod
-    def get_tag_list(tags):
-        if isinstance(tags, basestring):
-            tag_slugs = tags.split('/')
-            return [Tag.objects.get(slug=slug) for slug in tag_slugs]
-        else:
-            return TagBase.get_tag_list(tags)
-
-
-class Book(models.Model):
-    title = models.CharField(_('title'), max_length=120)
-    slug = models.SlugField(_('slug'), unique=True, db_index=True)
-    description = models.TextField(_('description'), blank=True)
-    created_at = models.DateTimeField(_('creation date'), auto_now=True)
-    _short_html = models.TextField(_('short HTML'), editable=False)
-    
-    # Formats
-    xml_file = models.FileField(_('XML file'), upload_to='books/xml', blank=True)
-    pdf_file = models.FileField(_('PDF file'), upload_to='books/pdf', blank=True)
-    odt_file = models.FileField(_('ODT file'), upload_to='books/odt', blank=True)
-    html_file = models.FileField(_('HTML file'), upload_to='books/html', blank=True)
-    
-    parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
-    
-    objects = models.Manager()
-    tagged = managers.ModelTaggedItemManager(Tag)
-    tags = managers.TagDescriptor(Tag)
-    
-    def short_html(self):
-        if len(self._short_html):
-            return mark_safe(self._short_html)
-        else:
-            tags = self.tags.filter(~Q(category__in=('set', 'theme')))
-            tags = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) for tag in tags]
-
-            formats = []
-            if self.html_file:
-                formats.append(u'<a href="%s">Czytaj online</a>' % self.html_file.url)
-            if self.pdf_file:
-                formats.append(u'<a href="%s">Plik PDF</a>' % self.pdf_file.url)
-            if self.odt_file:
-                formats.append(u'<a href="%s">Plik ODT</a>' % self.odt_file.url)
-            
-            self._short_html = render_to_string('catalogue/book_short.html',
-                {'book': self, 'tags': tags, 'formats': formats})
-            self.save()
-            return mark_safe(self._short_html)
-    
-    def has_description(self):
-        return len(self.description) > 0
-    has_description.short_description = _('description')
-    has_description.boolean = True
-    
-    def has_pdf_file(self):
-        return bool(self.pdf_file)
-    has_pdf_file.short_description = 'PDF'
-    has_pdf_file.boolean = True
-    
-    def has_odt_file(self):
-        return bool(self.odt_file)
-    has_odt_file.short_description = 'ODT'
-    has_odt_file.boolean = True
-    
-    def has_html_file(self):
-        return bool(self.html_file)
-    has_html_file.short_description = 'HTML'
-    has_html_file.boolean = True
-
-    @staticmethod
-    def from_xml_file(xml_file):
-        from tempfile import NamedTemporaryFile
-        from slughifi import slughifi
-        from markupstring import MarkupString
-        
-        # Read book metadata
-        book_info = dcparser.parse(xml_file)
-        book = Book(title=book_info.title, slug=slughifi(book_info.title))
-        book.save()
-        
-        book_tags = []
-        for category in ('kind', 'genre', 'author', 'epoch'):    
-            tag_name = getattr(book_info, category)
-            tag_sort_key = tag_name
-            if category == 'author':
-                tag_sort_key = tag_name.last_name
-                tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name
-            tag, created = Tag.objects.get_or_create(name=tag_name,
-                slug=slughifi(tag_name), sort_key=slughifi(tag_sort_key), category=category)
-            tag.save()
-            book_tags.append(tag)
-        book.tags = book_tags
-        
-        if hasattr(book_info, 'parts'):
-            for part_url in book_info.parts:
-                base, slug = part_url.rsplit('/', 1)
-                child_book = Book.objects.get(slug=slug)
-                child_book.parent = book
-                child_book.save()
-        
-        # Save XML and HTML files
-        book.xml_file.save('%s.xml' % book.slug, File(file(xml_file)), save=False)
-        
-        html_file = NamedTemporaryFile()
-        html.transform(book.xml_file.path, html_file)
-        book.html_file.save('%s.html' % book.slug, File(html_file), save=False)
-        
-        # Extract fragments
-        closed_fragments, open_fragments = html.extract_fragments(book.html_file.path)
-        book_themes = []
-        for fragment in closed_fragments.values():
-            text = fragment.to_string()
-            short_text = ''
-            if (len(MarkupString(text)) > 240):
-                short_text = MarkupString(text)[:160]
-            new_fragment = Fragment(text=text, short_text=short_text, anchor=fragment.id, book=book)
-                
-            theme_names = [s.strip() for s in fragment.themes.split(',')]
-            themes = []
-            for theme_name in theme_names:
-                tag, created = Tag.objects.get_or_create(name=theme_name,
-                    slug=slughifi(theme_name), sort_key=slughifi(theme_name), category='theme')
-                tag.save()
-                themes.append(tag)
-            new_fragment.save()
-            new_fragment.tags = list(book.tags) + themes
-            book_themes += themes
-        
-        book_themes = set(book_themes)
-        book.tags = list(book.tags) + list(book_themes)
-        return book.save()
-    
-    @permalink
-    def get_absolute_url(self):
-        return ('catalogue.views.book_detail', [self.slug])
-        
-    class Meta:
-        ordering = ('title',)
-        verbose_name = _('book')
-        verbose_name_plural = _('books')
-
-    def __unicode__(self):
-        return self.title
-
-
-class Fragment(models.Model):
-    text = models.TextField()
-    short_text = models.TextField(editable=False)
-    _short_html = models.TextField(editable=False)
-    anchor = models.IntegerField()
-    book = models.ForeignKey(Book, related_name='fragments')
-
-    objects = models.Manager()
-    tagged = managers.ModelTaggedItemManager(Tag)
-    tags = managers.TagDescriptor(Tag)
-    
-    def short_html(self):
-        if len(self._short_html):
-            return mark_safe(self._short_html)
-        else:
-            book_authors = [u'<a href="%s">%s</a>' % (tag.get_absolute_url(), tag.name) 
-                for tag in self.book.tags if tag.category == 'author']
-            
-            self._short_html = render_to_string('catalogue/fragment_short.html',
-                {'fragment': self, 'book': self.book, 'book_authors': book_authors})
-            self.save()
-            return mark_safe(self._short_html)
-        
-    class Meta:
-        ordering = ('book', 'anchor',)
-        verbose_name = _('fragment')
-        verbose_name_plural = _('fragments')
-
diff --git a/catalogue/templatetags/__init__.py b/catalogue/templatetags/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/catalogue/templatetags/catalogue_tags.py b/catalogue/templatetags/catalogue_tags.py
deleted file mode 100644 (file)
index 12460a0..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-# -*- coding: utf-8 -*-
-from django import template
-from django.template import Node, Variable
-from django.utils.encoding import smart_str
-from django.core.urlresolvers import reverse
-from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
-from django.db.models import Q
-
-
-register = template.Library()
-
-
-class RegistrationForm(UserCreationForm):
-    def as_ul(self):
-        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>', '</li>', u' %s', False)
-
-
-class LoginForm(AuthenticationForm):
-    def as_ul(self):
-        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>', '</li>', u' %s', False)
-
-
-def iterable(obj):
-    try:
-        iter(obj)
-        return True
-    except TypeError:
-        return False
-
-
-def capfirst(text):
-    try:
-        return '%s%s' % (text[0].upper(), text[1:])
-    except IndexError:
-        return ''
-
-
-@register.simple_tag
-def title_from_tags(tags):
-    def split_tags(tags):
-        result = {}
-        for tag in tags:
-            result[tag.category] = tag
-        return result
-    
-    class Flection(object):
-        def get_case(self, name, flection):
-            return name
-    flection = Flection()
-    
-    self = split_tags(tags)
-    
-    title = u''
-    
-    # Specjalny przypadek oglądania wszystkich lektur na danej półce
-    if len(self) == 1 and 'set' in self:
-        return u'Półka %s' % self['set']
-    
-    # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
-    # jest wybrana przez użytkownika
-    if 'epoch' in self and len(self) == 1:
-        text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
-        return capfirst(text)
-    
-    # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
-    # są tylko rodzaj literacki i autor
-    if 'kind' in self and 'author' in self and len(self) == 2:
-        text = u'%s w twórczości %s' % (unicode(self['kind']), 
-            flection.get_case(unicode(self['author']), u'dopełniacz'))
-        return capfirst(text)
-    
-    # Przypadki ogólniejsze
-    if 'theme' in self:
-        title += u'Motyw %s' % unicode(self['theme'])
-    
-    if 'genre' in self:
-        if 'theme' in self:
-            title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
-        else:
-            title += unicode(self['genre'])
-            
-    if 'kind' in self or 'author' in self or 'epoch' in self:
-        if 'genre' in self or 'theme' in self:
-            if 'kind' in self:
-                title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
-            else:
-                title += u' w twórczości '
-        else:
-            title += u'%s ' % unicode(self.get('kind', u'twórczość'))
-            
-    if 'author' in self:
-        title += flection.get_case(unicode(self['author']), u'dopełniacz')
-    elif 'epoch' in self:
-        title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
-    
-    return capfirst(title)
-
-
-@register.simple_tag
-def user_creation_form():
-    return RegistrationForm(prefix='registration').as_ul()
-
-
-@register.simple_tag
-def authentication_form():
-    return LoginForm(prefix='login').as_ul()
-
-
-@register.inclusion_tag('catalogue/breadcrumbs.html')
-def breadcrumbs(tags, search_form=True):
-    from wolnelektury.catalogue.forms import SearchForm
-    context = {'tag_list': tags}
-    if search_form:
-        context['search_form'] = SearchForm(tags=tags)
-    return context
-
-
-@register.tag
-def catalogue_url(parser, token):
-    bits = token.split_contents()
-    tag_name = bits[0]
-    
-    tags_to_add = []
-    tags_to_remove = []
-    for bit in bits[1:]:
-        if bit[0] == '-':
-            tags_to_remove.append(bit[1:])
-        else:
-            tags_to_add.append(bit)
-    
-    return CatalogueURLNode(tags_to_add, tags_to_remove)
-
-
-class CatalogueURLNode(Node):
-    def __init__(self, tags_to_add, tags_to_remove):
-        self.tags_to_add = [Variable(tag) for tag in tags_to_add]
-        self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
-    
-    def render(self, context):
-        tags_to_add = []
-        tags_to_remove = []
-
-        for tag_variable in self.tags_to_add:
-            tag = tag_variable.resolve(context)
-            if isinstance(tag, (list, dict)):
-                tags_to_add += [t for t in tag]
-            else:
-                tags_to_add.append(tag)
-
-        for tag_variable in self.tags_to_remove:
-            tag = tag_variable.resolve(context)
-            if iterable(tag):
-                tags_to_remove += [t for t in tag]
-            else:
-                tags_to_remove.append(tag)
-            
-        tag_slugs = [tag.slug for tag in tags_to_add]
-        for tag in tags_to_remove:
-            try:
-                tag_slugs.remove(tag.slug)
-            except KeyError:
-                pass
-        
-        if len(tag_slugs) > 0:
-            return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
-        else:
-            return reverse('main_page')
-
-
-@register.inclusion_tag('catalogue/latest_blog_posts.html')
-def latest_blog_posts(feed_url, posts_to_show=5):
-    import feedparser
-    import datetime
-    
-    feed = feedparser.parse(feed_url)
-    posts = []
-    for i in range(posts_to_show):
-        pub_date = feed['entries'][i].updated_parsed
-        published = datetime.date(pub_date[0], pub_date[1], pub_date[2] )
-        posts.append({
-            'title': feed['entries'][i].title,
-            'summary': feed['entries'][i].summary,
-            'link': feed['entries'][i].link,
-            'date': published,
-            })
-    return {'posts': posts}
-
diff --git a/catalogue/urls.py b/catalogue/urls.py
deleted file mode 100644 (file)
index f477906..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.conf.urls.defaults import *
-
-
-urlpatterns = patterns('catalogue.views',
-    url(r'^$', 'main_page', name='main_page'),
-    url(r'^polki/$', 'user_shelves', name='user_shelves'),
-    url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
-    url(r'^lektury/', 'book_list', name='book_list'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'),
-    url(r'^fragment/(?P<id>[0-9]+)/polki/', 'fragment_sets', name='fragment_shelves'),
-    url(r'^polki/nowa/$', 'new_set', name='new_set'),
-    url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'),
-    url(r'^tags/$', 'tags_starting_with', name='hint'),
-    url(r'^szukaj/$', 'search', name='search'),
-    url(r'^(?P<tags>[a-zA-Z0-9-/]+)/$', 'tagged_object_list', name='tagged_object_list'),
-)
-
diff --git a/catalogue/utils.py b/catalogue/utils.py
deleted file mode 100644 (file)
index d1cee50..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
-def split_tags(tags):
-    result = {}
-    for tag in tags:
-        result.setdefault(tag.category, []).append(tag)
-    return result
-
diff --git a/catalogue/views.py b/catalogue/views.py
deleted file mode 100644 (file)
index ad9bde7..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.template import RequestContext
-from django.shortcuts import render_to_response, get_object_or_404
-from django.http import HttpResponse, HttpResponseRedirect, Http404
-from django.core.urlresolvers import reverse
-from django.db.models import Q
-from django.contrib.auth.decorators import login_required
-from django.utils.datastructures import SortedDict
-from django.views.decorators.http import require_POST
-from django.contrib import auth
-from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
-from django.utils import simplejson
-from django.utils.functional import Promise
-from django.utils.encoding import force_unicode
-
-from catalogue import models
-from catalogue import forms
-from catalogue.utils import split_tags
-from newtagging import views as newtagging_views
-
-
-class LazyEncoder(simplejson.JSONEncoder):
-    def default(self, obj):
-        if isinstance(obj, Promise):
-            return force_unicode(obj)
-        return obj
-
-
-def search(request):
-    query = request.GET.get('q', '')
-    tags = request.GET.get('tags', '')
-    if tags == '':
-        tags = []
-    
-    try:
-        tag_list = models.Tag.get_tag_list(tags)
-        tag = models.Tag.objects.get(name=query)
-    except models.Tag.DoesNotExist:
-        try:
-            book = models.Book.objects.get(title=query)
-            return HttpResponseRedirect(book.get_absolute_url())
-        except models.Book.DoesNotExist:
-            return HttpResponseRedirect(reverse('catalogue.views.main_page'))
-    else:
-        tag_list.append(tag)
-        return HttpResponseRedirect(reverse('catalogue.views.tagged_object_list', 
-            kwargs={'tags': '/'.join(tag.slug for tag in tag_list)}
-        ))
-
-
-def tags_starting_with(request):
-    try:
-        prefix = request.GET['q']
-        if len(prefix) < 2:
-            raise KeyError
-            
-        books = models.Book.objects.filter(title__icontains=prefix)
-        tags = models.Tag.objects.filter(name__icontains=prefix)
-        if request.user.is_authenticated():
-            tags = tags.filter(~Q(category='set') | Q(user=request.user))
-        else:
-            tags = tags.filter(~Q(category='set'))
-        
-        completions = [book.title for book in books] + [tag.name for tag in tags]
-
-        return HttpResponse('\n'.join(completions))    
-    
-    except KeyError:
-        return HttpResponse('')
-
-
-def main_page(request):    
-    if request.user.is_authenticated():
-        extra_where = '(NOT catalogue_tag.category = "set" OR catalogue_tag.user_id = %d)' % request.user.id
-    else:
-        extra_where = 'NOT catalogue_tag.category = "set"'
-    tags = models.Tag.objects.usage_for_model(models.Book, counts=True, extra={'where': [extra_where]})
-    fragment_tags = models.Tag.objects.usage_for_model(models.Fragment, counts=True,
-        extra={'where': ['catalogue_tag.category = "theme"']})
-    categories = split_tags(tags)
-    
-    form = forms.SearchForm()
-    return render_to_response('catalogue/main_page.html', locals(),
-        context_instance=RequestContext(request))
-
-
-def book_list(request):
-    books = models.Book.objects.all()
-    form = forms.SearchForm()
-    
-    books_by_first_letter = SortedDict()
-    for book in books:
-        books_by_first_letter.setdefault(book.title[0], []).append(book)
-    
-    return render_to_response('catalogue/book_list.html', locals(),
-        context_instance=RequestContext(request))
-
-
-def tagged_object_list(request, tags=''):
-    try:
-        tags = models.Tag.get_tag_list(tags)
-    except models.Tag.DoesNotExist:
-        raise Http404
-    
-    model = models.Book
-    theme_is_set = any(tag.category == 'theme' for tag in tags)
-    if theme_is_set:
-        model = models.Fragment
-    
-    if request.user.is_authenticated():
-        extra_where = '(NOT catalogue_tag.category = "set" OR catalogue_tag.user_id = %d)' % request.user.id
-    else:
-        extra_where = 'NOT catalogue_tag.category = "set"'
-    related_tags = models.Tag.objects.related_for_model(tags, model, counts=True, extra={'where': [extra_where]})
-    categories = split_tags(related_tags)
-
-    return newtagging_views.tagged_object_list(
-        request,
-        tag_model=models.Tag,
-        queryset_or_model=model,
-        tags=tags,
-        template_name='catalogue/tagged_object_list.html',
-        extra_context = {'categories': categories },
-    )
-
-
-def book_detail(request, slug):
-    book = get_object_or_404(models.Book, slug=slug)
-    tags = list(book.tags.filter(~Q(category='set')))
-    categories = split_tags(tags)
-    
-    return render_to_response('catalogue/book_detail.html', locals(),
-        context_instance=RequestContext(request))
-
-
-def logout_then_redirect(request):
-    auth.logout(request)
-    return HttpResponseRedirect(request.GET.get('next', '/'))
-
-
-@require_POST
-def register(request):
-    registration_form = UserCreationForm(request.POST, prefix='registration')
-    if registration_form.is_valid():
-        user = registration_form.save()
-        user = auth.authenticate(
-            username=registration_form.cleaned_data['username'], 
-            password=registration_form.cleaned_data['password1']
-        )
-        auth.login(request, user)
-        response_data = {'success': True, 'errors': {}}
-    else:
-        response_data = {'success': False, 'errors': registration_form.errors}
-    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
-
-
-@require_POST
-def login(request):
-    form = AuthenticationForm(data=request.POST, prefix='login')
-    if form.is_valid():
-        auth.login(request, form.get_user())
-        response_data = {'success': True, 'errors': {}}
-    else:
-        response_data = {'success': False, 'errors': form.errors}
-    return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
-
-
-def book_sets(request, slug):
-    book = get_object_or_404(models.Book, slug=slug)
-    user_sets = models.Tag.objects.filter(category='set', user=request.user)
-    book_sets = book.tags.filter(category='set', user=request.user)
-    
-    if not request.user.is_authenticated():
-        return HttpResponse('<p>Aby zarządzać swoimi półkami, musisz się zalogować.</p>')
-    
-    if request.method == 'POST':
-        form = forms.ObjectSetsForm(book, request.user, request.POST)
-        if form.is_valid():
-            book.tags = ([models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']] +
-                list(book.tags.filter(~Q(category='set') | ~Q(user=request.user))))
-            if request.is_ajax():
-                return HttpResponse('<p>Półki zostały zapisane.</p>')
-            else:
-                return HttpResponseRedirect('/')
-    else:
-        form = forms.ObjectSetsForm(book, request.user)
-        new_set_form = forms.NewSetForm()
-    
-    return render_to_response('catalogue/book_sets.html', locals(),
-        context_instance=RequestContext(request))
-
-
-def fragment_sets(request, id):
-    fragment = get_object_or_404(models.Fragment, pk=id)
-    user_sets = models.Tag.objects.filter(category='set', user=request.user)
-    fragment_sets = fragment.tags.filter(category='set', user=request.user)
-
-    if not request.user.is_authenticated():
-        return HttpResponse('<p>Aby zarządzać swoimi półkami, musisz się zalogować.</p>')
-
-    if request.method == 'POST':
-        form = forms.ObjectSetsForm(fragment, request.user, request.POST)
-        if form.is_valid():
-            fragment.tags = ([models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']] +
-                list(fragment.tags.filter(~Q(category='set') | ~Q(user=request.user))))
-            if request.is_ajax():
-                return HttpResponse('<p>Półki zostały zapisane.</p>')
-            else:
-                return HttpResponseRedirect('/')
-    else:
-        form = forms.ObjectSetsForm(fragment, request.user)
-        new_set_form = forms.NewSetForm()
-
-    return render_to_response('catalogue/fragment_sets.html', locals(),
-        context_instance=RequestContext(request))
-
-
-@login_required
-@require_POST
-def new_set(request):
-    new_set_form = forms.NewSetForm(request.POST)
-    if new_set_form.is_valid():
-        new_set = new_set_form.save(request.user)
-        return HttpResponse(u'<p>Półka <strong>%s</strong> została utworzona</p>' % new_set)
-    
-    return render_to_response('catalogue/book_sets.html', locals(),
-            context_instance=RequestContext(request))
-
-
-@login_required
-@require_POST
-def delete_shelf(request, slug):
-    user_set = get_object_or_404(models.Tag, slug=slug, category='set', user=request.user)
-    user_set.delete()
-    return HttpResponse(u'<p>Półka <strong>%s</strong> została usunięta</p>' % user_set.name)
-    
-    
-@login_required
-def user_shelves(request):
-    shelves = models.Tag.objects.filter(category='set', user=request.user)
-    new_set_form = forms.NewSetForm()
-    return render_to_response('catalogue/user_shelves.html', locals(),
-            context_instance=RequestContext(request))
-
diff --git a/chunks/__init__.py b/chunks/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/chunks/admin.py b/chunks/admin.py
deleted file mode 100644 (file)
index c44d5a0..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib import admin
-
-from chunks.models import Chunk
-
-
-class ChunkAdmin(admin.ModelAdmin):
-    list_display = ('key', 'description',)
-    search_fields = ('key', 'content',)
-
-admin.site.register(Chunk, ChunkAdmin)
-
diff --git a/chunks/models.py b/chunks/models.py
deleted file mode 100644 (file)
index 396d221..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-
-
-class Chunk(models.Model):
-    """
-    A Chunk is a piece of content associated with a unique key that can be inserted into
-    any template with the use of a special template tag.
-    """
-    key = models.CharField(_('key'), help_text=_('A unique name for this chunk of content'), primary_key=True, max_length=255)
-    description = models.CharField(_('description'), blank=True, max_length=255)
-    content = models.TextField(_('content'), blank=True)
-
-    class Meta:
-        ordering = ('key',)
-        verbose_name = _('chunk')
-        verbose_name_plural = _('chunks')
-    
-    def __unicode__(self):
-        return u'%s' % (self.key,)
-
diff --git a/chunks/templatetags/__init__.py b/chunks/templatetags/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/chunks/templatetags/chunks.py b/chunks/templatetags/chunks.py
deleted file mode 100644 (file)
index f79d495..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-from django import template
-from django.db import models
-from django.core.cache import cache
-
-register = template.Library()
-
-Chunk = models.get_model('chunks', 'chunk')
-CACHE_PREFIX = "chunk_"
-
-def do_get_chunk(parser, token):
-    # split_contents() knows not to split quoted strings.
-    tokens = token.split_contents()
-    if len(tokens) < 2 or len(tokens) > 3:
-        raise template.TemplateSyntaxError, "%r tag should have either 2 or 3 arguments" % (tokens[0],)
-    if len(tokens) == 2:
-        tag_name, key = tokens
-        cache_time = 0
-    if len(tokens) == 3:
-        tag_name, key, cache_time = tokens
-    # Check to see if the key is properly double/single quoted
-    if not (key[0] == key[-1] and key[0] in ('"', "'")):
-        raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
-    # Send key without quotes and caching time
-    return ChunkNode(key[1:-1], cache_time)
-
-class ChunkNode(template.Node):
-    def __init__(self, key, cache_time=0):
-       self.key = key
-       self.cache_time = cache_time
-    
-    def render(self, context):
-        try:
-            cache_key = CACHE_PREFIX + self.key
-            c = cache.get(cache_key)
-            if c is None:
-                c = Chunk.objects.get(key=self.key)
-                cache.set(cache_key, c, int(self.cache_time))
-            content = c.content
-        except Chunk.DoesNotExist:
-            n = Chunk(key=self.key)
-            n.save()
-            return ''
-        return content
-        
-register.tag('chunk', do_get_chunk)
diff --git a/compress/__init__.py b/compress/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/conf/__init__.py b/compress/conf/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/conf/settings.py b/compress/conf/settings.py
deleted file mode 100644 (file)
index f6949a2..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-from django.core.exceptions import ImproperlyConfigured
-from django.conf import settings
-
-COMPRESS = getattr(settings, 'COMPRESS', not settings.DEBUG)
-COMPRESS_AUTO = getattr(settings, 'COMPRESS_AUTO', True)
-COMPRESS_VERSION = getattr(settings, 'COMPRESS_VERSION', False)
-COMPRESS_VERSION_PLACEHOLDER = getattr(settings, 'COMPRESS_VERSION_PLACEHOLDER', '?')
-COMPRESS_VERSION_DEFAULT = getattr(settings, 'COMPRESS_VERSION_DEFAULT', '0')
-
-COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', ['compress.filters.csstidy.CSSTidyFilter'])
-COMPRESS_JS_FILTERS = getattr(settings, 'COMPRESS_JS_FILTERS', ['compress.filters.jsmin.JSMinFilter'])
-COMPRESS_CSS = getattr(settings, 'COMPRESS_CSS', {})
-COMPRESS_JS = getattr(settings, 'COMPRESS_JS', {})
-
-if COMPRESS_CSS_FILTERS is None:
-    COMPRESS_CSS_FILTERS = []
-
-if COMPRESS_JS_FILTERS is None:
-    COMPRESS_JS_FILTERS = []
-
-if COMPRESS_VERSION and not COMPRESS_AUTO:
-    raise ImproperlyConfigured('COMPRESS_AUTO needs to be True when using COMPRESS_VERSION.')
diff --git a/compress/filter_base.py b/compress/filter_base.py
deleted file mode 100644 (file)
index 9b98531..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-class FilterBase:
-    def __init__(self, verbose):
-        self.verbose = verbose
-
-    def filter_css(self, css):
-        raise NotImplementedError
-    def filter_js(self, js):
-        raise NotImplementedError
-        
-class FilterError(Exception):
-    """
-    This exception is raised when a filter fails
-    """
-    pass
\ No newline at end of file
diff --git a/compress/filters/__init__.py b/compress/filters/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/filters/csstidy/__init__.py b/compress/filters/csstidy/__init__.py
deleted file mode 100644 (file)
index d40e8ee..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-import os
-import warnings
-import tempfile
-
-from django.conf import settings
-
-from compress.filter_base import FilterBase
-
-BINARY = getattr(settings, 'CSSTIDY_BINARY', 'csstidy')
-ARGUMENTS = getattr(settings, 'CSSTIDY_ARGUMENTS', '--template=highest')
-
-warnings.simplefilter('ignore', RuntimeWarning)
-
-class CSSTidyFilter(FilterBase):
-    def filter_css(self, css):
-        tmp_file = tempfile.NamedTemporaryFile(mode='w+b')
-        tmp_file.write(css)
-        tmp_file.flush()
-
-        output_file = tempfile.NamedTemporaryFile(mode='w+b')
-        
-        command = '%s %s %s %s' % (BINARY, tmp_file.name, ARGUMENTS, output_file.name)
-        
-        command_output = os.popen(command).read()
-        
-        filtered_css = output_file.read()
-        output_file.close()
-        tmp_file.close()
-        
-        if self.verbose:
-            print command_output
-        
-        return filtered_css
diff --git a/compress/filters/csstidy_python/__init__.py b/compress/filters/csstidy_python/__init__.py
deleted file mode 100644 (file)
index 7d581ed..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-from django.conf import settings
-
-from compress.filter_base import FilterBase
-from compress.filters.csstidy_python.csstidy import CSSTidy
-
-COMPRESS_CSSTIDY_SETTINGS = getattr(settings, 'COMPRESS_CSSTIDY_SETTINGS', {})
-
-class CSSTidyFilter(FilterBase):
-    def filter_css(self, css):
-        tidy = CSSTidy()
-        
-        for k, v in COMPRESS_CSSTIDY_SETTINGS.items():
-            tidy.setSetting(k, v)
-
-        tidy.parse(css)
-
-        r = tidy.Output('string')
-        
-        return r
diff --git a/compress/filters/csstidy_python/csstidy.py b/compress/filters/csstidy_python/csstidy.py
deleted file mode 100644 (file)
index 6ae8dc7..0000000
+++ /dev/null
@@ -1,636 +0,0 @@
-# CSSTidy - CSS Parse
-#
-# CSS Parser class
-#
-# This file is part of CSSTidy.
-#
-# CSSTidy 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 2 of the License, or
-# (at your option) any later version.
-#
-# CSSTidy 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 CSSTidy if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#
-# @license http://opensource.org/licenses/gpl-license.php GNU Public License
-# @package csstidy
-# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
-
-import re
-
-from optimizer import CSSOptimizer
-from output import CSSPrinter
-import data
-from tools import SortedDict
-
-class CSSTidy(object):
-    #Saves the parsed CSS
-    _css = ""
-    _raw_css = SortedDict()
-    _optimized_css = SortedDict()
-
-    #List of Tokens
-    _tokens = []
-
-    #Printer class
-    _output = None
-
-    #Optimiser class
-    _optimizer = None
-
-    #Saves the CSS charset (@charset)
-    _charset = ''
-
-    #Saves all @import URLs
-    _import = []
-
-    #Saves the namespace
-    _namespace = ''
-
-    #Contains the version of csstidy
-    _version = '1.3'
-
-    #Stores the settings
-    _settings = {}
-
-    # Saves the parser-status.
-    #
-    # Possible values:
-    # - is = in selector
-    # - ip = in property
-    # - iv = in value
-    # - instr = in string (started at " or ' or ( )
-    # - ic = in comment (ignore everything)
-    # - at = in @-block
-    _status = 'is'
-
-    #Saves the current at rule (@media)
-    _at = ''
-
-    #Saves the current selector
-    _selector = ''
-
-    #Saves the current property
-    _property = ''
-
-    #Saves the position of , in selectors
-    _sel_separate = []
-
-    #Saves the current value
-    _value = ''
-
-    #Saves the current sub-value
-    _sub_value = ''
-
-    #Saves all subvalues for a property.
-    _sub_value_arr = []
-
-    #Saves the char which opened the last string
-    _str_char = ''
-    _cur_string = ''
-
-    #Status from which the parser switched to ic or instr
-    _from = ''
-
-    #Variable needed to manage string-in-strings, for example url("foo.png")
-    _str_in_str = False
-
-    #=True if in invalid at-rule
-    _invalid_at = False
-
-    #=True if something has been added to the current selector
-    _added = False
-
-    #Saves the message log
-    _log = SortedDict()
-
-    #Saves the line number
-    _line = 1
-
-    def __init__(self):
-        self._settings['remove_bslash'] = True
-        self._settings['compress_colors'] = True
-        self._settings['compress_font-weight'] = True
-        self._settings['lowercase_s'] = False
-        self._settings['optimise_shorthands'] = 2
-        self._settings['remove_last_'] = False
-        self._settings['case_properties'] = 1
-        self._settings['sort_properties'] = False
-        self._settings['sort_selectors'] = False
-        self._settings['merge_selectors'] = 2
-        self._settings['discard_invalid_properties'] = False
-        self._settings['css_level'] = 'CSS2.1'
-        self._settings['preserve_css'] = False
-        self._settings['timestamp'] = False
-        self._settings['template'] = 'highest_compression'
-
-        #Maps self._status to methods
-        self.__statusMethod = {'is':self.__parseStatus_is, 'ip': self.__parseStatus_ip, 'iv':self.__parseStatus_iv, 'instr':self.__parseStatus_instr, 'ic':self.__parseStatus_ic, 'at':self.__parseStatus_at}
-
-        self._output = CSSPrinter(self)
-        self._optimizer = CSSOptimizer(self)
-
-    #Public Methods
-    def getSetting(self, setting):
-        return self._settings.get(setting, False)
-
-    #Set the value of a setting.
-    def setSetting(self, setting, value):
-        self._settings[setting] = value
-        return True
-
-    def log(self, message, ttype, line = -1):
-        if line == -1:
-            line = self._line
-
-        line = int(line)
-
-        add = {'m': message, 't': ttype}
-
-        if not self._log.has_key(line):
-            self._log[line] = []
-            self._log[line].append(add)
-        elif add not in self._log[line]:
-            self._log[line].append(add)
-
-
-    #Checks if a character is escaped (and returns True if it is)
-    def escaped(self, string, pos):
-        return not (string[pos-1] != '\\' or self.escaped(string, pos-1))
-
-    #Adds CSS to an existing media/selector
-    def merge_css_blocks(self, media, selector, css_add):
-        for prop, value in css_add.iteritems():
-            self.__css_add_property(media, selector, prop, value, False)
-
-    #Checks if $value is !important.
-    def is_important(self, value):
-        return '!important' in value.lower()
-
-    #Returns a value without !important
-    def gvw_important(self, value):
-        if self.is_important(value):
-            ret = value.strip()
-            ret = ret[0:-9]
-            ret = ret.strip()
-            ret = ret[0:-1]
-            ret = ret.strip()
-            return ret
-
-        return value
-
-    def parse(self, cssString):
-        #Switch from \r\n to \n
-        self._css = cssString.replace("\r\n", "\n") + ' '
-        self._raw_css = {}
-        self._optimized_css = {}
-        self._curComment = ''
-
-        #Start Parsing
-        i = 0
-        while i < len(cssString):
-            if self._css[i] == "\n" or self._css[i] == "\r":
-                self._line += 1
-
-            i += self.__statusMethod[self._status](i)
-
-            i += 1;
-
-        self._optimized_css = self._optimizer.optimize(self._raw_css)
-
-    def parseFile(self, filename):
-        try:
-            f = open(filename, "r")
-            self.parse(f.read())
-        finally:
-            f.close()
-
-    #Private Methods
-    def __parseStatus_is(self, idx):
-        """
-            Parse in Selector
-        """
-        ret = 0
-
-        if self.__is_token(self._css, idx):
-            if self._css[idx] == '/' and self._css[idx+1] == '*' and self._selector.strip() == '':
-                self._status = 'ic'
-                self._from = 'is'
-                return 1
-
-            elif self._css[idx] == '@' and self._selector.strip() == '':
-                #Check for at-rule
-                self._invalid_at = True
-
-                for name, ttype in data.at_rules.iteritems():
-                    if self._css[idx+1:len(name)].lower() == name.lower():
-                        if ttype == 'at':
-                            self._at = '@' + name
-                        else:
-                            self._selector = '@' + name
-
-                        self._status = ttype
-                        self._invalid_at = False
-                        ret += len(name)
-
-                if self._invalid_at:
-                    self._selector = '@'
-                    invalid_at_name = ''
-                    for j in xrange(idx+1, len(self._css)):
-                        if not self._css[j].isalpha():
-                            break;
-
-                        invalid_at_name += self._css[j]
-
-                    self.log('Invalid @-rule: ' + invalid_at_name + ' (removed)', 'Warning')
-
-            elif self._css[idx] == '"' or self._css[idx] == "'":
-                self._cur_string = self._css[idx]
-                self._status = 'instr'
-                self._str_char = self._css[idx]
-                self._from = 'is'
-
-            elif self._invalid_at and self._css[idx] == ';':
-                self._invalid_at = False
-                self._status = 'is'
-
-            elif self._css[idx] == '{':
-                self._status = 'ip'
-                self.__add_token(data.SEL_START, self._selector)
-                self._added = False;
-
-            elif self._css[idx] == '}':
-                self.__add_token(data.AT_END, self._at)
-                self._at = ''
-                self._selector = ''
-                self._sel_separate = []
-
-            elif self._css[idx] == ',':
-                self._selector = self._selector.strip() + ','
-                self._sel_separate.append(len(self._selector))
-
-            elif self._css[idx] == '\\':
-                self._selector += self.__unicode(idx)
-
-            #remove unnecessary universal selector,  FS#147
-            elif not (self._css[idx] == '*' and self._css[idx+1] in ('.', '#', '[', ':')):
-                self._selector += self._css[idx]
-
-        else:
-            lastpos = len(self._selector)-1
-
-            if lastpos == -1 or not ((self._selector[lastpos].isspace() or self.__is_token(self._selector, lastpos) and self._selector[lastpos] == ',') and self._css[idx].isspace()):
-                self._selector += self._css[idx]
-
-        return ret
-
-    def __parseStatus_ip(self, idx):
-        """
-            Parse in property
-        """
-        if self.__is_token(self._css, idx):
-            if (self._css[idx] == ':' or self._css[idx] == '=') and self._property != '':
-                self._status = 'iv'
-
-                if not self.getSetting('discard_invalid_properties') or self.__property_is_valid(self._property):
-                    self.__add_token(data.PROPERTY, self._property)
-
-            elif self._css[idx] == '/' and self._css[idx+1] == '*' and self._property == '':
-                self._status = 'ic'
-                self._from = 'ip'
-                return 1
-
-            elif self._css[idx] == '}':
-                self.__explode_selectors()
-                self._status = 'is'
-                self._invalid_at = False
-                self.__add_token(data.SEL_END, self._selector)
-                self._selector = ''
-                self._property = ''
-
-            elif self._css[idx] == ';':
-                self._property = ''
-
-            elif self._css[idx] == '\\':
-                self._property += self.__unicode(idx)
-
-        elif not self._css[idx].isspace():
-            self._property += self._css[idx]
-
-        return 0
-
-    def __parseStatus_iv(self, idx):
-        """
-            Parse in value
-        """
-        pn = (( self._css[idx] == "\n" or self._css[idx] == "\r") and self.__property_is_next(idx+1) or idx == len(self._css)) #CHECK#
-        if self.__is_token(self._css, idx) or pn:
-            if self._css[idx] == '/' and self._css[idx+1] == '*':
-                self._status = 'ic'
-                self._from = 'iv'
-                return 1
-
-            elif self._css[idx] == '"' or self._css[idx] == "'" or self._css[idx] == '(':
-                self._cur_string = self._css[idx]
-                self._str_char = ')' if self._css[idx] == '(' else self._css[idx]
-                self._status = 'instr'
-                self._from = 'iv'
-
-            elif self._css[idx] == ',':
-                self._sub_value = self._sub_value.strip() + ','
-
-            elif self._css[idx] == '\\':
-                self._sub_value += self.__unicode(idx)
-
-            elif self._css[idx] == ';' or pn:
-                if len(self._selector) > 0 and self._selector[0] == '@' and data.at_rules.has_key(self._selector[1:]) and data.at_rules[self._selector[1:]] == 'iv':
-                    self._sub_value_arr.append(self._sub_value.strip())
-
-                    self._status = 'is'
-
-                    if '@charset' in self._selector:
-                        self._charset = self._sub_value_arr[0]
-
-                    elif '@namespace' in self._selector:
-                        self._namespace = ' '.join(self._sub_value_arr)
-
-                    elif '@import' in self._selector:
-                        self._import.append(' '.join(self._sub_value_arr))
-
-
-                    self._sub_value_arr = []
-                    self._sub_value = ''
-                    self._selector = ''
-                    self._sel_separate = []
-
-                else:
-                    self._status = 'ip'
-
-            elif self._css[idx] != '}':
-                self._sub_value += self._css[idx]
-
-            if (self._css[idx] == '}' or self._css[idx] == ';' or pn) and self._selector != '':
-                if self._at == '':
-                    self._at = data.DEFAULT_AT
-
-                #case settings
-                if self.getSetting('lowercase_s'):
-                    self._selector = self._selector.lower()
-
-                self._property = self._property.lower()
-
-                if self._sub_value != '':
-                    self._sub_value_arr.append(self._sub_value)
-                    self._sub_value = ''
-
-                self._value = ' '.join(self._sub_value_arr)
-
-
-                self._selector = self._selector.strip()
-
-                valid = self.__property_is_valid(self._property)
-
-                if (not self._invalid_at or self.getSetting('preserve_css')) and (not self.getSetting('discard_invalid_properties') or valid):
-                    self.__css_add_property(self._at, self._selector, self._property, self._value)
-                    self.__add_token(data.VALUE, self._value)
-
-                if not valid:
-                    if self.getSetting('discard_invalid_properties'):
-                        self.log('Removed invalid property: ' + self._property, 'Warning')
-
-                    else:
-                        self.log('Invalid property in ' + self.getSetting('css_level').upper() + ': ' + self._property, 'Warning')
-
-                self._property = '';
-                self._sub_value_arr = []
-                self._value = ''
-
-            if self._css[idx] == '}':
-                self.__explode_selectors()
-                self.__add_token(data.SEL_END, self._selector)
-                self._status = 'is'
-                self._invalid_at = False
-                self._selector = ''
-
-        elif not pn:
-            self._sub_value += self._css[idx]
-
-            if self._css[idx].isspace():
-                if self._sub_value != '':
-                    self._sub_value_arr.append(self._sub_value)
-                    self._sub_value = ''
-
-        return 0
-
-    def __parseStatus_instr(self, idx):
-        """
-            Parse in String
-        """
-        if self._str_char == ')' and (self._css[idx] == '"' or self._css[idx] == "'") and not self.escaped(self._css, idx):
-            self._str_in_str = not self._str_in_str
-
-        temp_add = self._css[idx] # ...and no not-escaped backslash at the previous position
-        if (self._css[idx] == "\n" or self._css[idx] == "\r") and not (self._css[idx-1] == '\\' and not self.escaped(self._css, idx-1)):
-            temp_add = "\\A "
-            self.log('Fixed incorrect newline in string', 'Warning')
-
-        if not (self._str_char == ')' and self._css[idx].isspace() and not self._str_in_str):
-            self._cur_string += temp_add
-
-        if self._css[idx] == self._str_char and not self.escaped(self._css, idx) and not self._str_in_str:
-            self._status = self._from
-            regex = re.compile(r'([\s]+)', re.I | re.U | re.S)
-            if regex.match(self._cur_string) is None and self._property != 'content':
-                if self._str_char == '"' or self._str_char == "'":
-                    self._cur_string = self._cur_string[1:-1]
-
-                elif len(self._cur_string) > 3 and (self._cur_string[1] == '"' or self._cur_string[1] == "'"):
-                    self._cur_string = self._cur_string[0] + self._cur_string[2:-2] + self._cur_string[-1]
-
-            if self._from == 'iv':
-                self._sub_value += self._cur_string
-
-            elif self._from == 'is':
-                self._selector += self._cur_string
-
-        return 0
-
-    def __parseStatus_ic(self, idx):
-        """
-            Parse css In Comment
-        """
-        if self._css[idx] == '*' and self._css[idx+1] == '/':
-            self._status = self._from
-            self.__add_token(data.COMMENT, self._curComment)
-            self._curComment = ''
-            return 1
-
-        else:
-            self._curComment += self._css[idx]
-
-        return 0
-
-    def __parseStatus_at(self, idx):
-        """
-            Parse in at-block
-        """
-        if self.__is_token(string, idx):
-            if self._css[idx] == '/' and self._css[idx+1] == '*':
-                self._status = 'ic'
-                self._from = 'at'
-                return 1
-
-            elif self._css[i] == '{':
-                self._status = 'is'
-                self.__add_token(data.AT_START, self._at)
-
-            elif self._css[i] == ',':
-                self._at = self._at.strip() + ','
-
-            elif self._css[i] == '\\':
-                self._at += self.__unicode(i)
-        else:
-            lastpos = len(self._at)-1
-            if not (self._at[lastpos].isspace() or self.__is_token(self._at, lastpos) and self._at[lastpos] == ',') and self._css[i].isspace():
-                self._at += self._css[i]
-
-        return 0
-
-    def __explode_selectors(self):
-        #Explode multiple selectors
-        if self.getSetting('merge_selectors') == 1:
-            new_sels = []
-            lastpos = 0;
-            self._sel_separate.append(len(self._selector))
-
-            for num in xrange(len(self._sel_separate)):
-                pos = self._sel_separate[num]
-                if num == (len(self._sel_separate)): #CHECK#
-                    pos += 1
-
-                new_sels.append(self._selector[lastpos:(pos-lastpos-1)])
-                lastpos = pos
-
-            if len(new_sels) > 1:
-                for selector in new_sels:
-                    self.merge_css_blocks(self._at, selector, self._raw_css[self._at][self._selector])
-
-                del self._raw_css[self._at][self._selector]
-
-        self._sel_separate = []
-
-    #Adds a property with value to the existing CSS code
-    def __css_add_property(self, media, selector, prop, new_val):
-        if self.getSetting('preserve_css') or new_val.strip() == '':
-            return
-
-        if not self._raw_css.has_key(media):
-            self._raw_css[media] = SortedDict()
-
-        if not self._raw_css[media].has_key(selector):
-            self._raw_css[media][selector] = SortedDict()
-
-        self._added = True
-        if self._raw_css[media][selector].has_key(prop):
-            if (self.is_important(self._raw_css[media][selector][prop]) and self.is_important(new_val)) or not self.is_important(self._raw_css[media][selector][prop]):
-                del self._raw_css[media][selector][prop]
-                self._raw_css[media][selector][prop] = new_val.strip()
-
-        else:
-            self._raw_css[media][selector][prop] = new_val.strip()
-
-    #Checks if the next word in a string from pos is a CSS property
-    def __property_is_next(self, pos):
-        istring = self._css[pos: len(self._css)]
-        pos = istring.find(':')
-        if pos == -1:
-            return False;
-
-        istring = istring[:pos].strip().lower()
-        if data.all_properties.has_key(istring):
-            self.log('Added semicolon to the end of declaration', 'Warning')
-            return True
-
-        return False;
-
-    #Checks if a property is valid
-    def __property_is_valid(self, prop):
-        return (data.all_properties.has_key(prop) and data.all_properties[prop].find(self.getSetting('css_level').upper()) != -1)
-
-    #Adds a token to self._tokens
-    def __add_token(self, ttype, cssdata, do=False):
-        if self.getSetting('preserve_css') or do:
-            if ttype == data.COMMENT:
-                token = [ttype, cssdata]
-            else:
-                token = [ttype, cssdata.strip()]
-
-            self._tokens.append(token)
-
-    #Parse unicode notations and find a replacement character
-    def __unicode(self, idx):
-       ##FIX##
-       return ''
-
-    #Starts parsing from URL
-    ##USED?
-    def __parse_from_url(self, url):
-        try:
-            if "http" in url.lower() or "https" in url.lower():
-                f = urllib.urlopen(url)
-            else:
-                f = open(url)
-
-            data = f.read()
-            return self.parse(data)
-        finally:
-            f.close()
-
-    #Checks if there is a token at the current position
-    def __is_token(self, string, idx):
-        return (string[idx] in data.tokens and not self.escaped(string, idx))
-
-
-    #Property Methods
-    def _getOutput(self):
-        self._output.prepare(self._optimized_css)
-        return self._output.render
-
-    def _getLog(self):
-        ret = ""
-        ks = self._log.keys()
-        ks.sort()
-        for line in ks:
-            for msg in self._log[line]:
-                ret += "Type: " + msg['t'] + "\n"
-                ret += "Message: " + msg['m'] + "\n"
-            ret += "\n"
-
-        return ret
-
-    def _getCSS(self):
-        return self._css
-
-
-    #Properties
-    Output = property(_getOutput, None)
-    Log = property(_getLog, None)
-    CSS = property(_getCSS, None)
-
-
-if __name__ == '__main__':
-    import sys
-    tidy = CSSTidy()
-    f = open(sys.argv[1], "r")
-    css = f.read()
-    f.close()
-    tidy.parse(css)
-    tidy.Output('file', filename="Stylesheet.min.css")
-    print tidy.Output()
-    #print tidy._import
\ No newline at end of file
diff --git a/compress/filters/csstidy_python/data.py b/compress/filters/csstidy_python/data.py
deleted file mode 100644 (file)
index bd728cb..0000000
+++ /dev/null
@@ -1,421 +0,0 @@
-# Various CSS Data for CSSTidy
-#
-# This file is part of CSSTidy.
-#
-# CSSTidy 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 2 of the License, or
-# (at your option) any later version.
-#
-# CSSTidy 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 CSSTidy; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#
-# @license http://opensource.org/licenses/gpl-license.php GNU Public License
-# @package csstidy
-# @author Florian Schmitz (floele at gmail dot com) 2005
-
-AT_START    = 1
-AT_END      = 2
-SEL_START   = 3
-SEL_END     = 4
-PROPERTY    = 5
-VALUE       = 6
-COMMENT     = 7
-DEFAULT_AT  = 41
-
-# All whitespace allowed in CSS
-#
-# @global array whitespace
-# @version 1.0
-whitespace = frozenset([' ',"\n","\t","\r","\x0B"])
-
-# All CSS tokens used by csstidy
-#
-# @global string tokens
-# @version 1.0
-tokens = '/@}{;:=\'"(,\\!$%&)#+.<>?[]^`|~'
-
-# All CSS units (CSS 3 units included)
-#
-# @see compress_numbers()
-# @global array units
-# @version 1.0
-units = frozenset(['in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz'])
-
-# Available at-rules
-#
-# @global array at_rules
-# @version 1.0
-at_rules = {'page':'is', 'font-face':'is', 'charset':'iv', 'import':'iv', 'namespace':'iv', 'media':'at'}
-
-# Properties that need a value with unit
-#
-# @todo CSS3 properties
-# @see compress_numbers()
-# @global array unit_values
-# @version 1.2
-unit_values = frozenset(['background', 'background-position', 'border', 'border-top', 'border-right', 'border-bottom',
-                                    'border-left', 'border-width', 'border-top-width', 'border-right-width', 'border-left-width',
-                                    'border-bottom-width', 'bottom', 'border-spacing', 'font-size','height', 'left', 'margin', 'margin-top',
-                                    'margin-right', 'margin-bottom', 'margin-left', 'max-height', 'max-width', 'min-height', 'min-width',
-                                    'outline-width', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left','position',
-                                    'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width'
-                                    ])
-
-
-# Properties that allow <color> as value
-#
-# @todo CSS3 properties
-# @see compress_numbers()
-# @global array color_values
-# @version 1.0
-color_values = frozenset(['background-color', 'border-color', 'border-top-color', 'border-right-color',
-                                        'border-bottom-color', 'border-left-color', 'color', 'outline-color'])
-
-
-# Default values for the background properties
-#
-# @todo Possibly property names will change during CSS3 development
-# @global array background_prop_default
-# @see dissolve_short_bg()
-# @see merge_bg()
-# @version 1.0
-background_prop_default = {}
-background_prop_default['background-image'] = 'none'
-background_prop_default['background-size'] = 'auto'
-background_prop_default['background-repeat'] = 'repeat'
-background_prop_default['background-position'] = '0 0'
-background_prop_default['background-attachment'] = 'scroll'
-background_prop_default['background-clip'] = 'border'
-background_prop_default['background-origin'] = 'padding'
-background_prop_default['background-color'] = 'transparent'
-
-# A list of non-W3C color names which get replaced by their hex-codes
-#
-# @global array replace_colors
-# @see cut_color()
-# @version 1.0
-replace_colors = {}
-replace_colors['aliceblue'] = '#F0F8FF'
-replace_colors['antiquewhite'] = '#FAEBD7'
-replace_colors['aquamarine'] = '#7FFFD4'
-replace_colors['azure'] = '#F0FFFF'
-replace_colors['beige'] = '#F5F5DC'
-replace_colors['bisque'] = '#FFE4C4'
-replace_colors['blanchedalmond'] = '#FFEBCD'
-replace_colors['blueviolet'] = '#8A2BE2'
-replace_colors['brown'] = '#A52A2A'
-replace_colors['burlywood'] = '#DEB887'
-replace_colors['cadetblue'] = '#5F9EA0'
-replace_colors['chartreuse'] = '#7FFF00'
-replace_colors['chocolate'] = '#D2691E'
-replace_colors['coral'] = '#FF7F50'
-replace_colors['cornflowerblue'] = '#6495ED'
-replace_colors['cornsilk'] = '#FFF8DC'
-replace_colors['crimson'] = '#DC143C'
-replace_colors['cyan'] = '#00FFFF'
-replace_colors['darkblue'] = '#00008B'
-replace_colors['darkcyan'] = '#008B8B'
-replace_colors['darkgoldenrod'] = '#B8860B'
-replace_colors['darkgray'] = '#A9A9A9'
-replace_colors['darkgreen'] = '#006400'
-replace_colors['darkkhaki'] = '#BDB76B'
-replace_colors['darkmagenta'] = '#8B008B'
-replace_colors['darkolivegreen'] = '#556B2F'
-replace_colors['darkorange'] = '#FF8C00'
-replace_colors['darkorchid'] = '#9932CC'
-replace_colors['darkred'] = '#8B0000'
-replace_colors['darksalmon'] = '#E9967A'
-replace_colors['darkseagreen'] = '#8FBC8F'
-replace_colors['darkslateblue'] = '#483D8B'
-replace_colors['darkslategray'] = '#2F4F4F'
-replace_colors['darkturquoise'] = '#00CED1'
-replace_colors['darkviolet'] = '#9400D3'
-replace_colors['deeppink'] = '#FF1493'
-replace_colors['deepskyblue'] = '#00BFFF'
-replace_colors['dimgray'] = '#696969'
-replace_colors['dodgerblue'] = '#1E90FF'
-replace_colors['feldspar'] = '#D19275'
-replace_colors['firebrick'] = '#B22222'
-replace_colors['floralwhite'] = '#FFFAF0'
-replace_colors['forestgreen'] = '#228B22'
-replace_colors['gainsboro'] = '#DCDCDC'
-replace_colors['ghostwhite'] = '#F8F8FF'
-replace_colors['gold'] = '#FFD700'
-replace_colors['goldenrod'] = '#DAA520'
-replace_colors['greenyellow'] = '#ADFF2F'
-replace_colors['honeydew'] = '#F0FFF0'
-replace_colors['hotpink'] = '#FF69B4'
-replace_colors['indianred'] = '#CD5C5C'
-replace_colors['indigo'] = '#4B0082'
-replace_colors['ivory'] = '#FFFFF0'
-replace_colors['khaki'] = '#F0E68C'
-replace_colors['lavender'] = '#E6E6FA'
-replace_colors['lavenderblush'] = '#FFF0F5'
-replace_colors['lawngreen'] = '#7CFC00'
-replace_colors['lemonchiffon'] = '#FFFACD'
-replace_colors['lightblue'] = '#ADD8E6'
-replace_colors['lightcoral'] = '#F08080'
-replace_colors['lightcyan'] = '#E0FFFF'
-replace_colors['lightgoldenrodyellow'] = '#FAFAD2'
-replace_colors['lightgrey'] = '#D3D3D3'
-replace_colors['lightgreen'] = '#90EE90'
-replace_colors['lightpink'] = '#FFB6C1'
-replace_colors['lightsalmon'] = '#FFA07A'
-replace_colors['lightseagreen'] = '#20B2AA'
-replace_colors['lightskyblue'] = '#87CEFA'
-replace_colors['lightslateblue'] = '#8470FF'
-replace_colors['lightslategray'] = '#778899'
-replace_colors['lightsteelblue'] = '#B0C4DE'
-replace_colors['lightyellow'] = '#FFFFE0'
-replace_colors['limegreen'] = '#32CD32'
-replace_colors['linen'] = '#FAF0E6'
-replace_colors['magenta'] = '#FF00FF'
-replace_colors['mediumaquamarine'] = '#66CDAA'
-replace_colors['mediumblue'] = '#0000CD'
-replace_colors['mediumorchid'] = '#BA55D3'
-replace_colors['mediumpurple'] = '#9370D8'
-replace_colors['mediumseagreen'] = '#3CB371'
-replace_colors['mediumslateblue'] = '#7B68EE'
-replace_colors['mediumspringgreen'] = '#00FA9A'
-replace_colors['mediumturquoise'] = '#48D1CC'
-replace_colors['mediumvioletred'] = '#C71585'
-replace_colors['midnightblue'] = '#191970'
-replace_colors['mintcream'] = '#F5FFFA'
-replace_colors['mistyrose'] = '#FFE4E1'
-replace_colors['moccasin'] = '#FFE4B5'
-replace_colors['navajowhite'] = '#FFDEAD'
-replace_colors['oldlace'] = '#FDF5E6'
-replace_colors['olivedrab'] = '#6B8E23'
-replace_colors['orangered'] = '#FF4500'
-replace_colors['orchid'] = '#DA70D6'
-replace_colors['palegoldenrod'] = '#EEE8AA'
-replace_colors['palegreen'] = '#98FB98'
-replace_colors['paleturquoise'] = '#AFEEEE'
-replace_colors['palevioletred'] = '#D87093'
-replace_colors['papayawhip'] = '#FFEFD5'
-replace_colors['peachpuff'] = '#FFDAB9'
-replace_colors['peru'] = '#CD853F'
-replace_colors['pink'] = '#FFC0CB'
-replace_colors['plum'] = '#DDA0DD'
-replace_colors['powderblue'] = '#B0E0E6'
-replace_colors['rosybrown'] = '#BC8F8F'
-replace_colors['royalblue'] = '#4169E1'
-replace_colors['saddlebrown'] = '#8B4513'
-replace_colors['salmon'] = '#FA8072'
-replace_colors['sandybrown'] = '#F4A460'
-replace_colors['seagreen'] = '#2E8B57'
-replace_colors['seashell'] = '#FFF5EE'
-replace_colors['sienna'] = '#A0522D'
-replace_colors['skyblue'] = '#87CEEB'
-replace_colors['slateblue'] = '#6A5ACD'
-replace_colors['slategray'] = '#708090'
-replace_colors['snow'] = '#FFFAFA'
-replace_colors['springgreen'] = '#00FF7F'
-replace_colors['steelblue'] = '#4682B4'
-replace_colors['tan'] = '#D2B48C'
-replace_colors['thistle'] = '#D8BFD8'
-replace_colors['tomato'] = '#FF6347'
-replace_colors['turquoise'] = '#40E0D0'
-replace_colors['violet'] = '#EE82EE'
-replace_colors['violetred'] = '#D02090'
-replace_colors['wheat'] = '#F5DEB3'
-replace_colors['whitesmoke'] = '#F5F5F5'
-replace_colors['yellowgreen'] = '#9ACD32'
-
-#A list of optimized colors
-optimize_colors = {}
-optimize_colors['black'] = '#000'
-optimize_colors['fuchsia'] = '#F0F'
-optimize_colors['white'] = '#FFF'
-optimize_colors['yellow'] = '#FF0'
-optimize_colors['cyan'] = '#0FF'
-optimize_colors['magenta'] = '#F0F'
-optimize_colors['lightslategray'] = '#789'
-
-optimize_colors['#800000'] = 'maroon'
-optimize_colors['#FFA500'] = 'orange'
-optimize_colors['#808000'] = 'olive'
-optimize_colors['#800080'] = 'purple'
-optimize_colors['#008000'] = 'green'
-optimize_colors['#000080'] = 'navy'
-optimize_colors['#008080'] = 'teal'
-optimize_colors['#C0C0C0'] = 'silver'
-optimize_colors['#808080'] = 'gray'
-optimize_colors['#4B0082'] = 'indigo'
-optimize_colors['#FFD700'] = 'gold'
-optimize_colors['#A52A2A'] = 'brown'
-optimize_colors['#00FFFF'] = 'cyan'
-optimize_colors['#EE82EE'] = 'violet'
-optimize_colors['#DA70D6'] = 'orchid'
-optimize_colors['#FFE4C4'] = 'bisque'
-optimize_colors['#F0E68C'] = 'khaki'
-optimize_colors['#F5DEB3'] = 'wheat'
-optimize_colors['#FF7F50'] = 'coral'
-optimize_colors['#F5F5DC'] = 'beige'
-optimize_colors['#F0FFFF'] = 'azure'
-optimize_colors['#A0522D'] = 'sienna'
-optimize_colors['#CD853F'] = 'peru'
-optimize_colors['#FFFFF0'] = 'ivory'
-optimize_colors['#DDA0DD'] = 'plum'
-optimize_colors['#D2B48C'] = 'tan'
-optimize_colors['#FFC0CB'] = 'pink'
-optimize_colors['#FFFAFA'] = 'snow'
-optimize_colors['#FA8072'] = 'salmon'
-optimize_colors['#FF6347'] = 'tomato'
-optimize_colors['#FAF0E6'] = 'linen'
-optimize_colors['#F00'] = 'red'
-
-
-# A list of all shorthand properties that are devided into four properties and/or have four subvalues
-#
-# @global array shorthands
-# @todo Are there new ones in CSS3?
-# @see dissolve_4value_shorthands()
-# @see merge_4value_shorthands()
-# @version 1.0
-shorthands = {}
-shorthands['border-color'] = ['border-top-color','border-right-color','border-bottom-color','border-left-color']
-shorthands['border-style'] = ['border-top-style','border-right-style','border-bottom-style','border-left-style']
-shorthands['border-width'] = ['border-top-width','border-right-width','border-bottom-width','border-left-width']
-shorthands['margin'] = ['margin-top','margin-right','margin-bottom','margin-left']
-shorthands['padding'] = ['padding-top','padding-right','padding-bottom','padding-left']
-shorthands['-moz-border-radius'] = 0
-
-# All CSS Properties. Needed for csstidy::property_is_next()
-#
-# @global array all_properties
-# @todo Add CSS3 properties
-# @version 1.0
-# @see csstidy::property_is_next()
-all_properties = {}
-all_properties['background'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['background-color'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['background-image'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['background-position'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-top'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-right'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-left'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-color'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-top-color'] = 'CSS2.0,CSS2.1'
-all_properties['border-bottom-color'] = 'CSS2.0,CSS2.1'
-all_properties['border-left-color'] = 'CSS2.0,CSS2.1'
-all_properties['border-right-color'] = 'CSS2.0,CSS2.1'
-all_properties['border-style'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-top-style'] = 'CSS2.0,CSS2.1'
-all_properties['border-right-style'] = 'CSS2.0,CSS2.1'
-all_properties['border-left-style'] = 'CSS2.0,CSS2.1'
-all_properties['border-bottom-style'] = 'CSS2.0,CSS2.1'
-all_properties['border-width'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['border-collapse'] = 'CSS2.0,CSS2.1'
-all_properties['border-spacing'] = 'CSS2.0,CSS2.1'
-all_properties['bottom'] = 'CSS2.0,CSS2.1'
-all_properties['caption-side'] = 'CSS2.0,CSS2.1'
-all_properties['content'] = 'CSS2.0,CSS2.1'
-all_properties['clear'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['clip'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['color'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['counter-reset'] = 'CSS2.0,CSS2.1'
-all_properties['counter-increment'] = 'CSS2.0,CSS2.1'
-all_properties['cursor'] = 'CSS2.0,CSS2.1'
-all_properties['empty-cells'] = 'CSS2.0,CSS2.1'
-all_properties['display'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['direction'] = 'CSS2.0,CSS2.1'
-all_properties['float'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['font'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['font-family'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['font-style'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['font-stretch'] = 'CSS2.0'
-all_properties['font-size-adjust'] = 'CSS2.0'
-all_properties['font-size'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['height'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['left'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['line-height'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['list-style'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['margin'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['marks'] = 'CSS1.0,CSS2.0'
-all_properties['marker-offset'] = 'CSS2.0'
-all_properties['max-height'] = 'CSS2.0,CSS2.1'
-all_properties['max-width'] = 'CSS2.0,CSS2.1'
-all_properties['min-height'] = 'CSS2.0,CSS2.1'
-all_properties['min-width'] = 'CSS2.0,CSS2.1'
-all_properties['overflow'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['orphans'] = 'CSS2.0,CSS2.1'
-all_properties['outline'] = 'CSS2.0,CSS2.1'
-all_properties['outline-width'] = 'CSS2.0,CSS2.1'
-all_properties['outline-style'] = 'CSS2.0,CSS2.1'
-all_properties['outline-color'] = 'CSS2.0,CSS2.1'
-all_properties['padding'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['page-break-inside'] = 'CSS2.0,CSS2.1'
-all_properties['page'] = 'CSS2.0'
-all_properties['position'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['quotes'] = 'CSS2.0,CSS2.1'
-all_properties['right'] = 'CSS2.0,CSS2.1'
-all_properties['size'] = 'CSS1.0,CSS2.0'
-all_properties['speak-header'] = 'CSS2.0,CSS2.1'
-all_properties['table-layout'] = 'CSS2.0,CSS2.1'
-all_properties['top'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['text-align'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['text-shadow'] = 'CSS2.0'
-all_properties['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['white-space'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['unicode-bidi'] = 'CSS2.0,CSS2.1'
-all_properties['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['visibility'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['width'] = 'CSS1.0,CSS2.0,CSS2.1'
-all_properties['widows'] = 'CSS2.0,CSS2.1'
-all_properties['z-index'] = 'CSS1.0,CSS2.0,CSS2.1'
-
-# Speech #
-all_properties['volume'] = 'CSS2.0,CSS2.1'
-all_properties['speak'] = 'CSS2.0,CSS2.1'
-all_properties['pause'] = 'CSS2.0,CSS2.1'
-all_properties['pause-before'] = 'CSS2.0,CSS2.1'
-all_properties['pause-after'] = 'CSS2.0,CSS2.1'
-all_properties['cue'] = 'CSS2.0,CSS2.1'
-all_properties['cue-before'] = 'CSS2.0,CSS2.1'
-all_properties['cue-after'] = 'CSS2.0,CSS2.1'
-all_properties['play-during'] = 'CSS2.0,CSS2.1'
-all_properties['azimuth'] = 'CSS2.0,CSS2.1'
-all_properties['elevation'] = 'CSS2.0,CSS2.1'
-all_properties['speech-rate'] = 'CSS2.0,CSS2.1'
-all_properties['voice-family'] = 'CSS2.0,CSS2.1'
-all_properties['pitch'] = 'CSS2.0,CSS2.1'
-all_properties['pitch-range'] = 'CSS2.0,CSS2.1'
-all_properties['stress'] = 'CSS2.0,CSS2.1'
-all_properties['richness'] = 'CSS2.0,CSS2.1'
-all_properties['speak-punctuation'] = 'CSS2.0,CSS2.1'
-all_properties['speak-numeral'] = 'CSS2.0,CSS2.1'
\ No newline at end of file
diff --git a/compress/filters/csstidy_python/optimizer.py b/compress/filters/csstidy_python/optimizer.py
deleted file mode 100644 (file)
index 7cd284c..0000000
+++ /dev/null
@@ -1,383 +0,0 @@
-# CSSTidy - CSS Optimizer
-#
-# CSS Optimizer class
-#
-# This file is part of CSSTidy.
-#
-# CSSTidy 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 2 of the License, or
-# (at your option) any later version.
-#
-# CSSTidy 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 CSSTidy if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#
-# @license http://opensource.org/licenses/gpl-license.php GNU Public License
-# @package csstidy
-# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
-
-import data
-from tools import SortedDict
-
-
-class CSSOptimizer(object):
-    def __init__(self, parser):
-        #raw_css is a dict
-        self.parser = parser
-        self._optimized_css = SortedDict
-
-
-#PUBLIC METHODS
-    def optimize(self, raw_css):
-        if self.parser.getSetting('preserve_css'):
-            return raw_css
-
-        self._optimized_css = raw_css
-
-        if self.parser.getSetting('merge_selectors') == 2:
-            self.__merge_selectors()
-
-        ##OPTIMIZE##
-        for media, css in self._optimized_css.iteritems():
-            for selector, cssdata in css.iteritems():
-                if self.parser.getSetting('optimise_shorthands') >= 1:
-                    cssdata = self.__merge_4value_shorthands(cssdata)
-
-                if self.parser.getSetting('optimise_shorthands') >= 2:
-                    cssdata = self.__merge_bg(cssdata)
-
-                for item, value in cssdata.iteritems():
-                    value = self.__compress_numbers(item, value)
-                    value = self.__compress_important(value)
-
-                    if item in data.color_values and self.parser.getSetting('compress_colors'):
-                        old = value[:]
-                        value = self.__compress_color(value)
-                        if old != value:
-                            self.parser.log('In "' + selector + '" Optimised ' + item + ': Changed ' + old + ' to ' + value, 'Information')
-
-                    if item == 'font-weight' and self.parser.getSetting('compress_font-weight'):
-                        if value  == 'bold':
-                            value = '700'
-                            self.parser.log('In "' + selector + '" Optimised font-weight: Changed "bold" to "700"', 'Information')
-
-                        elif value == 'normal':
-                            value = '400'
-                            self.parser.log('In "' + selector + '" Optimised font-weight: Changed "normal" to "400"', 'Information')
-
-                    self._optimized_css[media][selector][item] = value
-
-
-        return self._optimized_css
-
-
-#PRIVATE METHODS
-    def __merge_bg(self, cssdata):
-        """
-            Merges all background properties
-            @cssdata (dict) is a dictionary of the selector properties
-        """
-        #Max number of background images. CSS3 not yet fully implemented
-        img = 1
-        clr = 1
-        bg_img_list = []
-        if cssdata.has_key('background-image'):
-            img = len(cssdata['background-image'].split(','))
-            bg_img_list = self.parser.gvw_important(cssdata['background-image']).split(',')
-
-        elif cssdata.has_key('background-color'):
-            clr = len(cssdata['background-color'].split(','))
-
-
-        number_of_values = max(img, clr, 1)
-
-        new_bg_value = ''
-        important = ''
-
-        for i in xrange(number_of_values):
-            for bg_property, default_value in data.background_prop_default.iteritems():
-                #Skip if property does not exist
-                if not cssdata.has_key(bg_property):
-                    continue
-
-                cur_value = cssdata[bg_property]
-
-                #Skip some properties if there is no background image
-                if (len(bg_img_list) > i and bg_img_list[i] == 'none') and bg_property in frozenset(['background-size', 'background-position', 'background-attachment', 'background-repeat']):
-                    continue
-
-                #Remove !important
-                if self.parser.is_important(cur_value):
-                    important = ' !important'
-                    cur_value = self.parser.gvw_important(cur_value)
-
-                #Do not add default values
-                if cur_value == default_value:
-                    continue
-
-                temp = cur_value.split(',')
-
-                if len(temp) > i:
-                    if bg_property == 'background-size':
-                        new_bg_value += '(' + temp[i] + ') '
-
-                    else:
-                        new_bg_value += temp[i] + ' '
-
-            new_bg_value = new_bg_value.strip()
-            if i != (number_of_values-1):
-                new_bg_value += ','
-
-        #Delete all background-properties
-        for bg_property, default_value in data.background_prop_default.iteritems():
-            try:
-                del cssdata[bg_property]
-            except:
-                pass
-
-        #Add new background property
-        if new_bg_value != '':
-            cssdata['background'] = new_bg_value + important
-
-        return cssdata
-
-    def __merge_4value_shorthands(self, cssdata):
-        """
-            Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
-            @cssdata (dict) is a dictionary of the selector properties
-        """
-        for key, value in data.shorthands.iteritems():
-            important = ''
-            if value != 0 and cssdata.has_key(value[0]) and cssdata.has_key(value[1]) and cssdata.has_key(value[2]) and cssdata.has_key(value[3]):
-                cssdata[key] = ''
-
-                for i in xrange(4):
-                    val = cssdata[value[i]]
-                    if self.parser.is_important(val):
-                        important = '!important'
-                        cssdata[key] += self.parser.gvw_important(val) + ' '
-
-                    else:
-                        cssdata[key] += val + ' '
-
-                    del cssdata[value[i]]
-            if cssdata.has_key(key):
-                cssdata[key] = self.__shorthand(cssdata[key] + important.strip())
-
-        return cssdata
-
-
-    def __merge_selectors(self):
-        """
-            Merges selectors with same properties. Example: a{color:red} b{color:red} . a,b{color:red}
-            Very basic and has at least one bug. Hopefully there is a replacement soon.
-            @selector_one (string) is the current selector
-            @value_one (dict) is a dictionary of the selector properties
-            Note: Currently is the elements of a selector are identical, but in a different order, they are not merged
-        """
-
-        ##OPTIMIZE##
-        ##FIX##
-
-        raw_css = self._optimized_css.copy()
-        delete = []
-        add = SortedDict()
-        for media, css in raw_css.iteritems():
-            for selector_one, value_one in css.iteritems():
-                newsel = selector_one
-
-                for selector_two, value_two in css.iteritems():
-                    if selector_one == selector_two:
-                        #We need to skip self
-                        continue
-
-                    if value_one == value_two:
-                        #Ok, we need to merge these two selectors
-                        newsel += ', ' + selector_two
-                        delete.append((media, selector_two))
-
-
-        if not add.has_key(media):
-            add[media] = SortedDict()
-
-        add[media][newsel] = value_one
-        delete.append((media, selector_one))
-
-        for item in delete:
-            try:
-                del self._optimized_css[item[0]][item[1]]
-            except:
-                #Must have already been deleted
-                continue
-
-        for media, css in add.iteritems():
-            self._optimized_css[media].update(css)
-
-
-
-    def __shorthand(self, value):
-        """
-            Compresses shorthand values. Example: margin:1px 1px 1px 1px . margin:1px
-            @value (string)
-        """
-
-        ##FIX##
-
-        important = '';
-        if self.parser.is_important(value):
-            value_list = self.parser.gvw_important(value)
-            important = '!important'
-        else:
-            value_list = value
-
-        ret = value
-        value_list = value_list.split(' ')
-
-        if len(value_list) == 4:
-            if value_list[0] == value_list[1] and value_list[0] == value_list[2] and value_list[0] == value_list[3]:
-                ret = value_list[0] + important
-
-            elif value_list[1] == value_list[3] and value_list[0] == value_list[2]:
-                ret = value_list[0] + ' ' + value_list[1] + important
-
-            elif value_list[1] == value_list[3]:
-                ret = value_list[0] + ' ' + value_list[1] + ' ' + value_list[2] + important
-
-        elif len(value_list) == 3:
-            if value_list[0] == value_list[1] and value_list[0] == value_list[2]:
-                ret = value_list[0] + important
-
-            elif value_list[0] == value_list[2]:
-                return value_list[0] + ' ' + value_list[1] + important
-
-        elif len(value_list) == 2:
-            if value_list[0] == value_list[1]:
-                ret = value_list[0] + important
-
-        if ret != value:
-            self.parser.log('Optimised shorthand notation: Changed "' + value + '" to "' + ret + '"', 'Information')
-
-        return ret
-
-    def __compress_important(self, value):
-        """
-            Removes unnecessary whitespace in ! important
-            @value (string)
-        """
-        if self.parser.is_important(value):
-            value = self.parser.gvw_important(value) + '!important'
-
-        return value
-
-    def __compress_numbers(self, prop, value):
-        """
-            Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
-            @value (string) is the posible number to be compressed
-        """
-
-        ##FIX##
-
-        value = value.split('/')
-
-        for l in xrange(len(value)):
-            #continue if no numeric value
-            if not (len(value[l]) > 0 and (value[l][0].isdigit() or value[l][0] in ('+', '-') )):
-                continue
-
-            #Fix bad colors
-            if prop in data.color_values:
-                value[l] = '#' + value[l]
-
-            is_floatable = False
-            try:
-                float(value[l])
-                is_floatable = True
-            except:
-                pass
-
-            if is_floatable and float(value[l]) == 0:
-                value[l] = '0'
-
-            elif value[l][0] != '#':
-                unit_found = False
-                for unit in data.units:
-                    pos = value[l].lower().find(unit)
-                    if pos != -1 and prop not in data.shorthands:
-                        value[l] = self.__remove_leading_zeros(float(value[l][:pos])) + unit
-                        unit_found = True
-                        break;
-
-                if not unit_found and prop in data.unit_values and prop not in data.shorthands:
-                    value[l] = self.__remove_leading_zeros(float(value[l])) + 'px'
-
-                elif not unit_found and prop not in data.shorthands:
-                    value[l] = self.__remove_leading_zeros(float(value[l]))
-
-
-        if len(value) > 1:
-            return '/'.join(value)
-
-        return value[0]
-
-    def __remove_leading_zeros(self, float_val):
-        """
-            Removes the leading zeros from a float value
-            @float_val (float)
-            @returns (string)
-        """
-        #Remove leading zero
-        if abs(float_val) < 1:
-            if float_val < 0:
-                float_val = '-' . str(float_val)[2:]
-            else:
-                float_val = str(float_val)[1:]
-
-        return str(float_val)
-
-    def __compress_color(self, color):
-        """
-            Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
-            @color (string) the {posible} color to change
-        """
-
-        #rgb(0,0,0) . #000000 (or #000 in this case later)
-        if color[:4].lower() == 'rgb(':
-            color_tmp = color[4:(len(color)-5)]
-            color_tmp = color_tmp.split(',')
-
-            for c in color_tmp:
-                c = c.strip()
-                if c[:-1] == '%':
-                    c = round((255*color_tmp[i])/100)
-
-                if color_tmp[i] > 255:
-                    color_tmp[i] = 255
-
-            color = '#'
-
-            for i in xrange(3):
-                if color_tmp[i] < 16:
-                    color += '0' + str(hex(color_tmp[i])).replace('0x', '')
-                else:
-                    color += str(hex(color_tmp[i])).replace('0x', '')
-
-        #Fix bad color names
-        if data.replace_colors.has_key(color.lower()):
-            color = data.replace_colors[color.lower()]
-
-        #aabbcc . #abc
-        if len(color) == 7:
-            color_temp = color.lower()
-            if color_temp[0] == '#' and color_temp[1] == color_temp[2] and color_temp[3] == color_temp[4] and color_temp[5] == color_temp[6]:
-                color = '#' + color[1] + color[3] + color[5]
-
-        if data.optimize_colors.has_key(color.lower()):
-            color = data.optimize_colors[color.lower()]
-
-        return color
\ No newline at end of file
diff --git a/compress/filters/csstidy_python/output.py b/compress/filters/csstidy_python/output.py
deleted file mode 100644 (file)
index 795a0d0..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-# CSSTidy - CSS Printer
-#
-# CSS Printer class
-#
-# This file is part of CSSTidy.
-#
-# CSSTidy 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 2 of the License, or
-# (at your option) any later version.
-#
-# CSSTidy 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 CSSTidy if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#
-# @license http://opensource.org/licenses/gpl-license.php GNU Public License
-# @package csstidy
-# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
-
-import data
-
-class CSSPrinter(object):
-    def __init__(self, parser):
-        self.parser = parser
-        self._css = {}
-        self.__renderMethods = {'string': self.__renderString, 'file': self.__renderFile}
-
-#PUBLIC METHODS
-    def prepare(self, css):
-        self._css = css
-
-    def render(self, output="string", *args, **kwargs):
-        return self.__renderMethods[output](*args, **kwargs)
-
-#PRIVATE METHODS
-    def __renderString(self, *args, **kwargs):
-        ##OPTIMIZE##
-        template = self.parser.getSetting('template')
-        ret = ""
-
-        if template == 'highest_compression':
-            top_line_end = ""
-            iner_line_end = ""
-            bottom_line_end = ""
-            indent = ""
-
-        elif template == 'high_compression':
-            top_line_end = "\n"
-            iner_line_end = ""
-            bottom_line_end = "\n"
-            indent = ""
-
-        elif template == 'default':
-            top_line_end = "\n"
-            iner_line_end = "\n"
-            bottom_line_end = "\n\n"
-            indent = ""
-
-        elif template == 'low_compression':
-            top_line_end = "\n"
-            iner_line_end = "\n"
-            bottom_line_end = "\n\n"
-            indent = "    "
-
-        if self.parser.getSetting('timestamp'):
-            ret += '/# CSSTidy ' + self.parser.version + ': ' + datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000") + ' #/' + top_line_end
-
-        for item in self.parser._import:
-            ret += '@import(' + item + ');' + top_line_end
-
-        for item in self.parser._charset:
-            ret += '@charset(' + item + ');' + top_line_end
-
-        for item in self.parser._namespace:
-            ret += '@namespace(' + item + ');' + top_line_end
-
-        for media, css in self._css.iteritems():
-            for selector, cssdata in css.iteritems():
-                ret += selector + '{' + top_line_end
-
-                for item, value in cssdata.iteritems():
-                    ret += indent +  item + ':' + value + ';' + iner_line_end
-
-                ret += '}' + bottom_line_end
-
-        return ret
-
-    def __renderFile(self, filename=None, *args, **kwargs):
-        if filename is None:
-            return self.__renderString()
-
-        try:
-            f = open(filename, "w")
-            f.write(self.__renderString())
-        finally:
-            f.close()
\ No newline at end of file
diff --git a/compress/filters/csstidy_python/tools.py b/compress/filters/csstidy_python/tools.py
deleted file mode 100644 (file)
index e62faef..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-
-class SortedDict(dict):
-    """
-    A dictionary that keeps its keys in the order in which they're inserted.
-    """
-    def __init__(self, data=None):
-        if data is None:
-            data = {}
-        super(SortedDict, self).__init__(data)
-        if isinstance(data, dict):
-            self.keyOrder = data.keys()
-        else:
-            self.keyOrder = []
-            for key, value in data:
-                if key not in self.keyOrder:
-                    self.keyOrder.append(key)
-
-    def __deepcopy__(self, memo):
-        from copy import deepcopy
-        return self.__class__([(key, deepcopy(value, memo))
-                               for key, value in self.iteritems()])
-
-    def __setitem__(self, key, value):
-        super(SortedDict, self).__setitem__(key, value)
-        if key not in self.keyOrder:
-            self.keyOrder.append(key)
-
-    def __delitem__(self, key):
-        super(SortedDict, self).__delitem__(key)
-        self.keyOrder.remove(key)
-
-    def __iter__(self):
-        for k in self.keyOrder:
-            yield k
-
-    def pop(self, k, *args):
-        result = super(SortedDict, self).pop(k, *args)
-        try:
-            self.keyOrder.remove(k)
-        except ValueError:
-            # Key wasn't in the dictionary in the first place. No problem.
-            pass
-        return result
-
-    def popitem(self):
-        result = super(SortedDict, self).popitem()
-        self.keyOrder.remove(result[0])
-        return result
-
-    def items(self):
-        return zip(self.keyOrder, self.values())
-
-    def iteritems(self):
-        for key in self.keyOrder:
-            yield key, super(SortedDict, self).__getitem__(key)
-
-    def keys(self):
-        return self.keyOrder[:]
-
-    def iterkeys(self):
-        return iter(self.keyOrder)
-
-    def values(self):
-        return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
-
-    def itervalues(self):
-        for key in self.keyOrder:
-            yield super(SortedDict, self).__getitem__(key)
-
-    def update(self, dict_):
-        for k, v in dict_.items():
-            self.__setitem__(k, v)
-
-    def setdefault(self, key, default):
-        if key not in self.keyOrder:
-            self.keyOrder.append(key)
-        return super(SortedDict, self).setdefault(key, default)
-
-    def value_for_index(self, index):
-        """Returns the value of the item at the given zero-based index."""
-        return self[self.keyOrder[index]]
-
-    def insert(self, index, key, value):
-        """Inserts the key, value pair before the item with the given index."""
-        if key in self.keyOrder:
-            n = self.keyOrder.index(key)
-            del self.keyOrder[n]
-            if n < index:
-                index -= 1
-        self.keyOrder.insert(index, key)
-        super(SortedDict, self).__setitem__(key, value)
-
-    def copy(self):
-        """Returns a copy of this object."""
-        # This way of initializing the copy means it works for subclasses, too.
-        obj = self.__class__(self)
-        obj.keyOrder = self.keyOrder[:]
-        return obj
-
-    def __repr__(self):
-        """
-        Replaces the normal dict.__repr__ with a version that returns the keys
-        in their sorted order.
-        """
-        return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
-
-    def clear(self):
-        super(SortedDict, self).clear()
-        self.keyOrder = []
\ No newline at end of file
diff --git a/compress/filters/jsmin/__init__.py b/compress/filters/jsmin/__init__.py
deleted file mode 100644 (file)
index d226200..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-from compress.filters.jsmin.jsmin import jsmin
-from compress.filter_base import FilterBase
-
-class JSMinFilter(FilterBase):
-    def filter_js(self, js):
-        return jsmin(js)
\ No newline at end of file
diff --git a/compress/filters/jsmin/jsmin.py b/compress/filters/jsmin/jsmin.py
deleted file mode 100644 (file)
index 4f9d384..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-#!/usr/bin/python
-
-# This code is original from jsmin by Douglas Crockford, it was translated to
-# Python by Baruch Even. The original code had the following copyright and
-# license.
-#
-# /* jsmin.c
-#    2007-05-22
-#
-# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy of
-# this software and associated documentation files (the "Software"), to deal in
-# the Software without restriction, including without limitation the rights to
-# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-# of the Software, and to permit persons to whom the Software is furnished to do
-# so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# The Software shall be used for Good, not Evil.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-# */
-
-from StringIO import StringIO
-
-def jsmin(js):
-    ins = StringIO(js)
-    outs = StringIO()
-    JavascriptMinify().minify(ins, outs)
-    str = outs.getvalue()
-    if len(str) > 0 and str[0] == '\n':
-        str = str[1:]
-    return str
-
-def isAlphanum(c):
-    """return true if the character is a letter, digit, underscore,
-           dollar sign, or non-ASCII character.
-    """
-    return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
-            (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
-
-class UnterminatedComment(Exception):
-    pass
-
-class UnterminatedStringLiteral(Exception):
-    pass
-
-class UnterminatedRegularExpression(Exception):
-    pass
-
-class JavascriptMinify(object):
-
-    def _outA(self):
-        self.outstream.write(self.theA)
-    def _outB(self):
-        self.outstream.write(self.theB)
-
-    def _get(self):
-        """return the next character from stdin. Watch out for lookahead. If
-           the character is a control character, translate it to a space or
-           linefeed.
-        """
-        c = self.theLookahead
-        self.theLookahead = None
-        if c == None:
-            c = self.instream.read(1)
-        if c >= ' ' or c == '\n':
-            return c
-        if c == '': # EOF
-            return '\000'
-        if c == '\r':
-            return '\n'
-        return ' '
-
-    def _peek(self):
-        self.theLookahead = self._get()
-        return self.theLookahead
-
-    def _next(self):
-        """get the next character, excluding comments. peek() is used to see
-           if a '/' is followed by a '/' or '*'.
-        """
-        c = self._get()
-        if c == '/':
-            p = self._peek()
-            if p == '/':
-                c = self._get()
-                while c > '\n':
-                    c = self._get()
-                return c
-            if p == '*':
-                c = self._get()
-                while 1:
-                    c = self._get()
-                    if c == '*':
-                        if self._peek() == '/':
-                            self._get()
-                            return ' '
-                    if c == '\000':
-                        raise UnterminatedComment()
-
-        return c
-
-    def _action(self, action):
-        """do something! What you do is determined by the argument:
-           1   Output A. Copy B to A. Get the next B.
-           2   Copy B to A. Get the next B. (Delete A).
-           3   Get the next B. (Delete B).
-           action treats a string as a single character. Wow!
-           action recognizes a regular expression if it is preceded by ( or , or =.
-        """
-        if action <= 1:
-            self._outA()
-
-        if action <= 2:
-            self.theA = self.theB
-            if self.theA == "'" or self.theA == '"':
-                while 1:
-                    self._outA()
-                    self.theA = self._get()
-                    if self.theA == self.theB:
-                        break
-                    if self.theA <= '\n':
-                        raise UnterminatedStringLiteral()
-                    if self.theA == '\\':
-                        self._outA()
-                        self.theA = self._get()
-
-
-        if action <= 3:
-            self.theB = self._next()
-            if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
-                                     self.theA == '=' or self.theA == ':' or
-                                     self.theA == '[' or self.theA == '?' or
-                                     self.theA == '!' or self.theA == '&' or
-                                     self.theA == '|' or self.theA == ';' or
-                                     self.theA == '{' or self.theA == '}' or
-                                     self.theA == '\n'):
-                self._outA()
-                self._outB()
-                while 1:
-                    self.theA = self._get()
-                    if self.theA == '/':
-                        break
-                    elif self.theA == '\\':
-                        self._outA()
-                        self.theA = self._get()
-                    elif self.theA <= '\n':
-                        raise UnterminatedRegularExpression()
-                    self._outA()
-                self.theB = self._next()
-
-
-    def _jsmin(self):
-        """Copy the input to the output, deleting the characters which are
-           insignificant to JavaScript. Comments will be removed. Tabs will be
-           replaced with spaces. Carriage returns will be replaced with linefeeds.
-           Most spaces and linefeeds will be removed.
-        """
-        self.theA = '\n'
-        self._action(3)
-
-        while self.theA != '\000':
-            if self.theA == ' ':
-                if isAlphanum(self.theB):
-                    self._action(1)
-                else:
-                    self._action(2)
-            elif self.theA == '\n':
-                if self.theB in ['{', '[', '(', '+', '-']:
-                    self._action(1)
-                elif self.theB == ' ':
-                    self._action(3)
-                else:
-                    if isAlphanum(self.theB):
-                        self._action(1)
-                    else:
-                        self._action(2)
-            else:
-                if self.theB == ' ':
-                    if isAlphanum(self.theA):
-                        self._action(1)
-                    else:
-                        self._action(3)
-                elif self.theB == '\n':
-                    if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
-                        self._action(1)
-                    else:
-                        if isAlphanum(self.theA):
-                            self._action(1)
-                        else:
-                            self._action(3)
-                else:
-                    self._action(1)
-
-    def minify(self, instream, outstream):
-        self.instream = instream
-        self.outstream = outstream
-        self.theA = '\n'
-        self.theB = None
-        self.theLookahead = None
-
-        self._jsmin()
-        self.instream.close()
-
-if __name__ == '__main__':
-    import sys
-    jsm = JavascriptMinify()
-    jsm.minify(sys.stdin, sys.stdout)
\ No newline at end of file
diff --git a/compress/filters/yui/__init__.py b/compress/filters/yui/__init__.py
deleted file mode 100644 (file)
index 1e2e711..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-import subprocess
-
-from django.conf import settings
-
-from compress.filter_base import FilterBase, FilterError
-
-BINARY = getattr(settings, 'COMPRESS_YUI_BINARY', 'java -jar yuicompressor.jar')
-CSS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_CSS_ARGUMENTS', '')
-JS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_JS_ARGUMENTS', '')
-
-class YUICompressorFilter(FilterBase):
-
-    def filter_common(self, content, type_, arguments):
-        command = '%s --type=%s %s' % (BINARY, type_, arguments)
-
-        if self.verbose:
-            command += ' --verbose'
-
-        p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
-        p.stdin.write(content)
-        p.stdin.close()
-
-        filtered_css = p.stdout.read()
-        p.stdout.close()
-
-        err = p.stderr.read()
-        p.stderr.close()
-
-        if p.wait() != 0:
-            if not err:
-                err = 'Unable to apply YUI Compressor filter'
-
-            raise FilterError(err)
-
-        if self.verbose:
-            print err
-
-        return filtered_css
-
-    def filter_js(self, js):
-        return self.filter_common(js, 'js', JS_ARGUMENTS)
-
-    def filter_css(self, css):
-        return self.filter_common(css, 'css', CSS_ARGUMENTS)
\ No newline at end of file
diff --git a/compress/management/__init__.py b/compress/management/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/management/commands/__init__.py b/compress/management/commands/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/management/commands/synccompress.py b/compress/management/commands/synccompress.py
deleted file mode 100644 (file)
index 6e31d25..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-from django.core.management.base import NoArgsCommand
-from optparse import make_option
-
-from django.conf import settings
-
-class Command(NoArgsCommand):
-    option_list = NoArgsCommand.option_list + (
-        make_option('--force', action='store_true', default=False, help='Force update of all files, even if the source files are older than the current compressed file.'),
-        make_option('--verbosity', action='store', dest='verbosity', default='1',
-            type='choice', choices=['0', '1', '2'],
-            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
-    )
-    help = 'Updates and compresses CSS and JavsScript on-demand, without restarting Django'
-    args = ''
-
-    def handle_noargs(self, **options):
-        
-        force = options.get('force', False)
-        verbosity = int(options.get('verbosity', 1))
-
-        from compress.utils import needs_update, filter_css, filter_js
-
-        for name, css in settings.COMPRESS_CSS.items():
-            u, version = needs_update(css['output_filename'], css['source_filenames'])
-
-            if (force or u) or verbosity >= 2:
-                msg = 'CSS Group \'%s\'' % name
-                print msg
-                print len(msg) * '-'
-                print "Version: %s" % version
-
-            if force or u:
-                filter_css(css, verbosity)
-
-            if (force or u) or verbosity >= 2:
-                print
-
-        for name, js in settings.COMPRESS_JS.items():
-            u, version = needs_update(js['output_filename'], js['source_filenames'])
-
-            if (force or u) or verbosity >= 2:
-                msg = 'JavaScript Group \'%s\'' % name
-                print msg
-                print len(msg) * '-'
-                print "Version: %s" % version
-
-            if force or u:
-                filter_js(js, verbosity)
-
-            if (force or u) or verbosity >= 2:
-                print
\ No newline at end of file
diff --git a/compress/models.py b/compress/models.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/signals.py b/compress/signals.py
deleted file mode 100644 (file)
index bd76a76..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-from django.dispatch import Signal
-
-css_filtered = Signal()
-js_filtered = Signal()
diff --git a/compress/templates/compress/css.html b/compress/templates/compress/css.html
deleted file mode 100644 (file)
index 68ddbac..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<link href="{{ url }}" rel="stylesheet" type="text/css" media="{{ media|default:"all" }}" />
\ No newline at end of file
diff --git a/compress/templates/compress/css_ie.html b/compress/templates/compress/css_ie.html
deleted file mode 100644 (file)
index 80372dc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<!--[if {{ condition|default:"IE" }}]><link href="{{ url }}" rel="stylesheet" type="text/css" media="{{ media|default:"all" }}" /><![endif]-->
\ No newline at end of file
diff --git a/compress/templates/compress/js.html b/compress/templates/compress/js.html
deleted file mode 100644 (file)
index bfa2b59..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<script type="text/javascript" src="{{ url }}"></script>
\ No newline at end of file
diff --git a/compress/templates/compress/js_ie.html b/compress/templates/compress/js_ie.html
deleted file mode 100644 (file)
index 8235fe2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<!--[if {{ condition|default:"IE" }}]><script type="text/javascript" src="{{ url }}"></script><![endif]-->
\ No newline at end of file
diff --git a/compress/templatetags/__init__.py b/compress/templatetags/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/compress/templatetags/compressed.py b/compress/templatetags/compressed.py
deleted file mode 100644 (file)
index 2a02009..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-import os
-
-from django import template
-
-from django.conf import settings as django_settings
-
-from compress.conf import settings
-from compress.utils import media_root, media_url, needs_update, filter_css, filter_js, get_output_filename, get_version
-
-register = template.Library()
-
-def render_common(template_name, obj, filename, version):
-    if settings.COMPRESS:
-        filename = get_output_filename(filename, version)
-
-    context = obj.get('extra_context', {})
-    context['url'] = media_url(filename)
-
-    return template.loader.render_to_string(template_name, context)
-
-def render_css(css, filename, version=None):
-    return render_common(css.get('template_name', 'compress/css.html'), css, filename, version)
-
-def render_js(js, filename, version=None):
-    return render_common(js.get('template_name', 'compress/js.html'), js, filename, version)
-
-class CompressedCSSNode(template.Node):
-    def __init__(self, name):
-        self.name = name
-
-    def render(self, context):
-        css_name = template.Variable(self.name).resolve(context)
-
-        try:
-            css = settings.COMPRESS_CSS[css_name]
-        except KeyError:
-            return '' # fail silently, do not return anything if an invalid group is specified
-
-        if settings.COMPRESS:
-
-            version = None
-
-            if settings.COMPRESS_AUTO:
-                u, version = needs_update(css['output_filename'], css['source_filenames'])
-                if u:
-                    filter_css(css)
-
-            return render_css(css, css['output_filename'], version)
-        else:
-            # output source files
-            r = ''
-            for source_file in css['source_filenames']:
-                r += render_css(css, source_file)
-
-            return r
-
-class CompressedJSNode(template.Node):
-    def __init__(self, name):
-        self.name = name
-
-    def render(self, context):
-        js_name = template.Variable(self.name).resolve(context)
-
-        try:
-            js = settings.COMPRESS_JS[js_name]
-        except KeyError:
-            return '' # fail silently, do not return anything if an invalid group is specified
-
-        if settings.COMPRESS:
-
-            version = None
-
-            if settings.COMPRESS_AUTO:
-                u, version = needs_update(js['output_filename'], js['source_filenames'])
-                if u:
-                    filter_js(js)
-
-            return render_js(js, js['output_filename'], version)
-        else:
-            # output source files
-            r = ''
-            for source_file in js['source_filenames']:
-                r += render_js(js, source_file)
-            return r
-
-#@register.tag
-def compressed_css(parser, token):
-    try:
-        tag_name, name = token.split_contents()
-    except ValueError:
-        raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_CSS setting' % token.split_contents()[0]
-
-    return CompressedCSSNode(name)
-compressed_css = register.tag(compressed_css)
-
-#@register.tag
-def compressed_js(parser, token):
-    try:
-        tag_name, name = token.split_contents()
-    except ValueError:
-        raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_JS setting' % token.split_contents()[0]
-
-    return CompressedJSNode(name)
-compressed_js = register.tag(compressed_js)
diff --git a/compress/utils.py b/compress/utils.py
deleted file mode 100644 (file)
index 1e0681f..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-import os
-import re
-import tempfile
-
-from django.conf import settings as django_settings
-from django.utils.http import urlquote
-from django.dispatch import dispatcher
-
-from compress.conf import settings
-from compress.signals import css_filtered, js_filtered
-
-def get_filter(compressor_class):
-    """
-    Convert a string version of a function name to the callable object.
-    """
-
-    if not hasattr(compressor_class, '__bases__'):
-
-        try:
-            compressor_class = compressor_class.encode('ascii')
-            mod_name, class_name = get_mod_func(compressor_class)
-            if class_name != '':
-                compressor_class = getattr(__import__(mod_name, {}, {}, ['']), class_name)
-        except (ImportError, AttributeError):
-            raise Exception('Failed to import filter %s' % compressor_class)
-
-    return compressor_class
-
-def get_mod_func(callback):
-    """
-    Converts 'django.views.news.stories.story_detail' to
-    ('django.views.news.stories', 'story_detail')
-    """
-
-    try:
-        dot = callback.rindex('.')
-    except ValueError:
-        return callback, ''
-    return callback[:dot], callback[dot+1:]
-
-def needs_update(output_file, source_files):
-    """
-    Scan the source files for changes and returns True if the output_file needs to be updated.
-    """
-
-    mtime = max_mtime(source_files)
-    version = get_version(mtime)
-
-    compressed_file_full = media_root(get_output_filename(output_file, version))
-
-    if not os.path.exists(compressed_file_full):
-        return True, version
-
-    # Check if the output file is outdated
-    return (os.stat(compressed_file_full).st_mtime < mtime), mtime
-
-def media_root(filename):
-    """
-    Return the full path to ``filename``. ``filename`` is a relative path name in MEDIA_ROOT
-    """
-    return os.path.join(django_settings.MEDIA_ROOT, filename)
-
-def media_url(url):
-    return django_settings.MEDIA_URL + urlquote(url)
-
-def concat(filenames, separator=''):
-    """
-    Concatenate the files from the list of the ``filenames``, ouput separated with ``separator``.
-    """
-    r = ''
-
-    for filename in filenames:
-        fd = open(media_root(filename), 'rb')
-        r += fd.read()
-        r += separator
-        fd.close()
-
-    return r
-
-def max_mtime(files):
-    return int(max([os.stat(media_root(f)).st_mtime for f in files]))
-
-def save_file(filename, contents):
-    fd = open(media_root(filename), 'wb+')
-    fd.write(contents)
-    fd.close()
-
-def get_output_filename(filename, version):
-    if settings.COMPRESS_VERSION and version is not None:
-        return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, get_version(version))
-    else:
-        return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, settings.COMPRESS_VERSION_DEFAULT)
-
-def get_version(version):
-    try:
-        return str(int(version))
-    except ValueError:
-        return str(version)
-
-def remove_files(path, filename, verbosity=0):    
-    regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'\d+'))))
-
-    for f in os.listdir(path):
-        if regex.match(f):
-            if verbosity >= 1:
-                print "Removing outdated file %s" % f
-
-            os.unlink(os.path.join(path, f))
-
-def filter_common(obj, verbosity, filters, attr, separator, signal):
-    output = concat(obj['source_filenames'], separator)
-    filename = get_output_filename(obj['output_filename'], get_version(max_mtime(obj['source_filenames'])))
-
-    if settings.COMPRESS_VERSION:
-        remove_files(os.path.dirname(media_root(filename)), obj['output_filename'], verbosity)
-
-    if verbosity >= 1:
-        print "Saving %s" % filename
-
-    for f in filters:
-        output = getattr(get_filter(f)(verbose=(verbosity >= 2)), attr)(output)
-
-    save_file(filename, output)
-    signal.send(None)
-
-def filter_css(css, verbosity=0):
-    return filter_common(css, verbosity, filters=settings.COMPRESS_CSS_FILTERS, attr='filter_css', separator='', signal=css_filtered)
-
-def filter_js(js, verbosity=0):
-    return filter_common(js, verbosity, filters=settings.COMPRESS_JS_FILTERS, attr='filter_js', separator=';', signal=js_filtered)
diff --git a/newtagging/__init__.py b/newtagging/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/newtagging/admin.py b/newtagging/admin.py
deleted file mode 100644 (file)
index 956d2cf..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.contrib import admin
-from django import forms
-from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext_lazy as _
-
-
-class FilteredSelectMultiple(forms.SelectMultiple):
-    """
-    A SelectMultiple with a JavaScript filter interface.
-
-    Note that the resulting JavaScript assumes that the SelectFilter2.js
-    library and its dependencies have been loaded in the HTML page.
-    """
-    def _media(self):
-        from django.conf import settings
-        js = ['js/SelectBox.js' , 'js/SelectFilter2.js']
-        return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
-    media = property(_media)
-    
-    def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
-        self.verbose_name = verbose_name
-        self.is_stacked = is_stacked
-        super(FilteredSelectMultiple, self).__init__(attrs, choices)
-
-    def render(self, name, value, attrs=None, choices=()):
-        from django.conf import settings
-        output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)]
-        output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {')
-        # TODO: "id_" is hard-coded here. This should instead use the correct
-        # API to determine the ID dynamically.
-        output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \
-            (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
-        return mark_safe(u''.join(output))
-
-
-class TaggableModelForm(forms.ModelForm):
-    tags = forms.MultipleChoiceField(label=_('tags').capitalize(), required=True, widget=FilteredSelectMultiple(_('tags'), False))
-
-    def __init__(self, *args, **kwargs):
-        if 'instance' in kwargs:
-            if 'initial' not in kwargs:
-                kwargs['initial'] = {}
-            kwargs['initial']['tags'] = [tag.id for tag in self.tag_model.objects.get_for_object(kwargs['instance'])]
-        super(TaggableModelForm, self).__init__(*args, **kwargs)
-        self.fields['tags'].choices = [(tag.id, tag.name) for tag in self.tag_model.objects.all()]
-    
-    def save(self, commit):
-        obj = super(TaggableModelForm, self).save()
-        tag_ids = self.cleaned_data['tags']
-        tags = self.tag_model.objects.filter(pk__in=tag_ids)
-        self.tag_model.objects.update_tags(obj, tags)
-        return obj
-
-    def save_m2m(self):
-        # TODO: Shouldn't be needed
-        pass
-
-
-class TaggableModelAdmin(admin.ModelAdmin):
-    form = TaggableModelForm
-    
-    def get_form(self, request, obj=None):
-        form = super(TaggableModelAdmin, self).get_form(request, obj)
-        form.tag_model = self.tag_model
-        return form
-
diff --git a/newtagging/managers.py b/newtagging/managers.py
deleted file mode 100644 (file)
index 1dbcb29..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-Custom managers for Django models registered with the tagging
-application.
-"""
-from django.contrib.contenttypes.models import ContentType
-from django.db import models
-
-
-class ModelTagManager(models.Manager):
-    """
-    A manager for retrieving tags for a particular model.
-    """
-    def __init__(self, tag_model):
-        super(ModelTagManager, self).__init__()
-        self.tag_model = tag_model
-    
-    def get_query_set(self):
-        content_type = ContentType.objects.get_for_model(self.model)
-        return self.tag_model.objects.filter(
-            items__content_type__pk=content_type.pk).distinct()
-    
-    def related(self, tags, *args, **kwargs):
-        return self.tag_model.objects.related_for_model(tags, self.model, *args, **kwargs)
-    
-    def usage(self, *args, **kwargs):
-        return self.tag_model.objects.usage_for_model(self.model, *args, **kwargs)
-
-
-class ModelTaggedItemManager(models.Manager):
-    """
-    A manager for retrieving model instances based on their tags.
-    """
-    def __init__(self, tag_model):
-        super(ModelTaggedItemManager, self).__init__()
-        self.intermediary_table_model = tag_model.objects.intermediary_table_model
-
-    def related_to(self, obj, queryset=None, num=None):
-        if queryset is None:
-            return self.intermediary_table_model.objects.get_related(obj, self.model, num=num)
-        else:
-            return self.intermediary_table_model.objects.get_related(obj, queryset, num=num)
-
-    def with_all(self, tags, queryset=None):
-        if queryset is None:
-            return self.intermediary_table_model.objects.get_by_model(self.model, tags)
-        else:
-            return self.intermediary_table_model.objects.get_by_model(queryset, tags)
-
-    def with_any(self, tags, queryset=None):
-        if queryset is None:
-            return self.intermediary_table_model.objects.get_union_by_model(self.model, tags)
-        else:
-            return self.intermediary_table_model.objects.get_union_by_model(queryset, tags)
-
-
-class TagDescriptor(object):
-    """
-    A descriptor which provides access to a ``ModelTagManager`` for
-    model classes and simple retrieval, updating and deletion of tags
-    for model instances.
-    """
-    def __init__(self, tag_model):
-        self.tag_model = tag_model
-    
-    def __get__(self, instance, owner):
-        if not instance:
-            tag_manager = ModelTagManager(self.tag_model)
-            tag_manager.model = owner
-            return tag_manager
-        else:
-            return self.tag_model.objects.get_for_object(instance)
-
-    def __set__(self, instance, value):
-        self.tag_model.objects.update_tags(instance, value)
-
-    def __del__(self, instance):
-        self.tag_model.objects.update_tags(instance, [])
-
diff --git a/newtagging/models.py b/newtagging/models.py
deleted file mode 100644 (file)
index 0c2b0e1..0000000
+++ /dev/null
@@ -1,510 +0,0 @@
-"""
-Models and managers for generic tagging.
-"""
-# Python 2.3 compatibility
-if not hasattr(__builtins__, 'set'):
-    from sets import Set as set
-
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django.db import connection, models
-from django.utils.translation import ugettext_lazy as _
-from django.db.models.base import ModelBase
-
-qn = connection.ops.quote_name
-
-try:
-    from django.db.models.query import parse_lookup
-except ImportError:
-    parse_lookup = None
-
-
-def get_queryset_and_model(queryset_or_model):
-    """
-    Given a ``QuerySet`` or a ``Model``, returns a two-tuple of
-    (queryset, model).
-
-    If a ``Model`` is given, the ``QuerySet`` returned will be created
-    using its default manager.
-    """
-    try:
-        return queryset_or_model, queryset_or_model.model
-    except AttributeError:
-        return queryset_or_model._default_manager.all(), queryset_or_model
-
-
-############
-# Managers #
-############
-class TagManager(models.Manager):
-    def __init__(self, intermediary_table_model):
-        super(TagManager, self).__init__()
-        self.intermediary_table_model = intermediary_table_model
-    
-    def update_tags(self, obj, tags):
-        """
-        Update tags associated with an object.
-        """
-        content_type = ContentType.objects.get_for_model(obj)
-        current_tags = list(self.filter(items__content_type__pk=content_type.pk,
-                                        items__object_id=obj.pk))
-        updated_tags = self.model.get_tag_list(tags)
-    
-        # Remove tags which no longer apply
-        tags_for_removal = [tag for tag in current_tags \
-                            if tag not in updated_tags]
-        if len(tags_for_removal):
-            self.intermediary_table_model._default_manager.filter(content_type__pk=content_type.pk,
-                                               object_id=obj.pk,
-                                               tag__in=tags_for_removal).delete()
-        # Add new tags
-        for tag in updated_tags:
-            if tag not in current_tags:
-                self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj)
-    
-    def get_for_object(self, obj):
-        """
-        Create a queryset matching all tags associated with the given
-        object.
-        """
-        ctype = ContentType.objects.get_for_model(obj)
-        return self.filter(items__content_type__pk=ctype.pk,
-                           items__object_id=obj.pk)
-    
-    def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None, extra=None):
-        """
-        Perform the custom SQL query for ``usage_for_model`` and
-        ``usage_for_queryset``.
-        """
-        if min_count is not None: counts = True
-
-        model_table = qn(model._meta.db_table)
-        model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column))
-        tag_columns = self._get_tag_columns()
-        
-        if extra is None: extra = {}
-        extra_where = ''
-        if 'where' in extra:
-            extra_where = 'AND ' + ' AND '.join(extra['where'])
-        
-        query = """
-        SELECT DISTINCT %(tag_columns)s%(count_sql)s
-        FROM
-            %(tag)s
-            INNER JOIN %(tagged_item)s
-                ON %(tag)s.id = %(tagged_item)s.tag_id
-            INNER JOIN %(model)s
-                ON %(tagged_item)s.object_id = %(model_pk)s
-            %%s
-        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
-            %%s
-            %(extra_where)s
-        GROUP BY %(tag)s.id, %(tag)s.name
-        %%s
-        ORDER BY %(tag)s.%(ordering)s ASC""" % {
-            'tag': qn(self.model._meta.db_table),
-            'ordering': ', '.join(qn(field) for field in self.model._meta.ordering),
-            'tag_columns': tag_columns,
-            'count_sql': counts and (', COUNT(%s)' % model_pk) or '',
-            'tagged_item': qn(self.intermediary_table_model._meta.db_table),
-            'model': model_table,
-            'model_pk': model_pk,
-            'extra_where': extra_where,
-            'content_type_id': ContentType.objects.get_for_model(model).pk,
-        }
-
-        min_count_sql = ''
-        if min_count is not None:
-            min_count_sql = 'HAVING COUNT(%s) >= %%s' % model_pk
-            params.append(min_count)
-
-        cursor = connection.cursor()
-        cursor.execute(query % (extra_joins, extra_criteria, min_count_sql), params)
-        tags = []
-        for row in cursor.fetchall():
-            t = self.model(*row[:len(self.model._meta.fields)])
-            if counts:
-                t.count = row[len(self.model._meta.fields)]
-            tags.append(t)
-        return tags
-
-    def usage_for_model(self, model, counts=False, min_count=None, filters=None, extra=None):
-        """
-        Obtain a list of tags associated with instances of the given
-        Model class.
-
-        If ``counts`` is True, a ``count`` attribute will be added to
-        each tag, indicating how many times it has been used against
-        the Model class in question.
-
-        If ``min_count`` is given, only tags which have a ``count``
-        greater than or equal to ``min_count`` will be returned.
-        Passing a value for ``min_count`` implies ``counts=True``.
-
-        To limit the tags (and counts, if specified) returned to those
-        used by a subset of the Model's instances, pass a dictionary
-        of field lookups to be applied to the given Model as the
-        ``filters`` argument.
-        """
-        if extra is None: extra = {}
-        if filters is None: filters = {}
-
-        if not parse_lookup:
-            # post-queryset-refactor (hand off to usage_for_queryset)
-            queryset = model._default_manager.filter()
-            for f in filters.items():
-                queryset.query.add_filter(f)
-            usage = self.usage_for_queryset(queryset, counts, min_count, extra)
-        else:
-            # pre-queryset-refactor
-            extra_joins = ''
-            extra_criteria = ''
-            params = []
-            if len(filters) > 0:
-                joins, where, params = parse_lookup(filters.items(), model._meta)
-                extra_joins = ' '.join(['%s %s AS %s ON %s' % (join_type, table, alias, condition)
-                                        for (alias, (table, join_type, condition)) in joins.items()])
-                extra_criteria = 'AND %s' % (' AND '.join(where))
-            usage = self._get_usage(model, counts, min_count, extra_joins, extra_criteria, params, extra)
-
-        return usage
-
-    def usage_for_queryset(self, queryset, counts=False, min_count=None, extra=None):
-        """
-        Obtain a list of tags associated with instances of a model
-        contained in the given queryset.
-
-        If ``counts`` is True, a ``count`` attribute will be added to
-        each tag, indicating how many times it has been used against
-        the Model class in question.
-
-        If ``min_count`` is given, only tags which have a ``count``
-        greater than or equal to ``min_count`` will be returned.
-        Passing a value for ``min_count`` implies ``counts=True``.
-        """
-        if parse_lookup:
-            raise AttributeError("'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django.")
-
-        extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:])
-        where, params = queryset.query.where.as_sql()
-        if where:
-            extra_criteria = 'AND %s' % where
-        else:
-            extra_criteria = ''
-        return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params, extra)
-
-    def related_for_model(self, tags, model, counts=False, min_count=None, extra=None):
-        """
-        Obtain a list of tags related to a given list of tags - that
-        is, other tags used by items which have all the given tags.
-
-        If ``counts`` is True, a ``count`` attribute will be added to
-        each tag, indicating the number of items which have it in
-        addition to the given list of tags.
-
-        If ``min_count`` is given, only tags which have a ``count``
-        greater than or equal to ``min_count`` will be returned.
-        Passing a value for ``min_count`` implies ``counts=True``.
-        """
-        if min_count is not None: counts = True
-        tags = self.model.get_tag_list(tags)
-        tag_count = len(tags)
-        tagged_item_table = qn(self.intermediary_table_model._meta.db_table)
-        tag_columns = self._get_tag_columns()
-        
-        if extra is None: extra = {}
-        extra_where = ''
-        if 'where' in extra:
-            extra_where = 'AND ' + ' AND '.join(extra['where'])
-        
-        query = """
-        SELECT %(tag_columns)s%(count_sql)s
-        FROM %(tagged_item)s INNER JOIN %(tag)s ON %(tagged_item)s.tag_id = %(tag)s.id
-        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
-          AND %(tagged_item)s.object_id IN
-          (
-              SELECT %(tagged_item)s.object_id
-              FROM %(tagged_item)s, %(tag)s
-              WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
-                AND %(tag)s.id = %(tagged_item)s.tag_id
-                AND %(tag)s.id IN (%(tag_id_placeholders)s)
-              GROUP BY %(tagged_item)s.object_id
-              HAVING COUNT(%(tagged_item)s.object_id) = %(tag_count)s
-          )
-          AND %(tag)s.id NOT IN (%(tag_id_placeholders)s)
-          %(extra_where)s
-        GROUP BY %(tag)s.id, %(tag)s.name
-        %(min_count_sql)s
-        ORDER BY %(tag)s.%(ordering)s ASC""" % {
-            'tag': qn(self.model._meta.db_table),
-            'ordering': ', '.join(qn(field) for field in self.model._meta.ordering),
-            'tag_columns': tag_columns,
-            'count_sql': counts and ', COUNT(%s.object_id)' % tagged_item_table or '',
-            'tagged_item': tagged_item_table,
-            'content_type_id': ContentType.objects.get_for_model(model).pk,
-            'tag_id_placeholders': ','.join(['%s'] * tag_count),
-            'extra_where': extra_where,
-            'tag_count': tag_count,
-            'min_count_sql': min_count is not None and ('HAVING COUNT(%s.object_id) >= %%s' % tagged_item_table) or '',
-        }
-
-        params = [tag.pk for tag in tags] * 2
-        if min_count is not None:
-            params.append(min_count)
-
-        cursor = connection.cursor()
-        cursor.execute(query, params)
-        related = []
-        for row in cursor.fetchall():
-            tag = self.model(*row[:len(self.model._meta.fields)])
-            if counts is True:
-                tag.count = row[len(self.model._meta.fields)]
-            related.append(tag)
-        return related
-
-    def _get_tag_columns(self):
-        tag_table = qn(self.model._meta.db_table)
-        return ', '.join('%s.%s' % (tag_table, qn(field.column)) for field in self.model._meta.fields)
-
-
-class TaggedItemManager(models.Manager):
-    """
-    FIXME There's currently no way to get the ``GROUP BY`` and ``HAVING``
-          SQL clauses required by many of this manager's methods into
-          Django's ORM.
-
-          For now, we manually execute a query to retrieve the PKs of
-          objects we're interested in, then use the ORM's ``__in``
-          lookup to return a ``QuerySet``.
-
-          Once the queryset-refactor branch lands in trunk, this can be
-          tidied up significantly.
-    """
-    def __init__(self, tag_model):
-        super(TaggedItemManager, self).__init__()
-        self.tag_model = tag_model
-    
-    def get_by_model(self, queryset_or_model, tags):
-        """
-        Create a ``QuerySet`` containing instances of the specified
-        model associated with a given tag or list of tags.
-        """
-        tags = self.tag_model.get_tag_list(tags)
-        tag_count = len(tags)
-        if tag_count == 0:
-            # No existing tags were given
-            queryset, model = get_queryset_and_model(queryset_or_model)
-            return model._default_manager.none()
-        elif tag_count == 1:
-            # Optimisation for single tag - fall through to the simpler
-            # query below.
-            tag = tags[0]
-        else:
-            return self.get_intersection_by_model(queryset_or_model, tags)
-
-        queryset, model = get_queryset_and_model(queryset_or_model)
-        content_type = ContentType.objects.get_for_model(model)
-        opts = self.model._meta
-        tagged_item_table = qn(opts.db_table)
-        return queryset.extra(
-            tables=[opts.db_table],
-            where=[
-                '%s.content_type_id = %%s' % tagged_item_table,
-                '%s.tag_id = %%s' % tagged_item_table,
-                '%s.%s = %s.object_id' % (qn(model._meta.db_table),
-                                          qn(model._meta.pk.column),
-                                          tagged_item_table)
-            ],
-            params=[content_type.pk, tag.pk],
-        )
-
-    def get_intersection_by_model(self, queryset_or_model, tags):
-        """
-        Create a ``QuerySet`` containing instances of the specified
-        model associated with *all* of the given list of tags.
-        """
-        tags = self.tag_model.get_tag_list(tags)
-        tag_count = len(tags)
-        queryset, model = get_queryset_and_model(queryset_or_model)
-
-        if not tag_count:
-            return model._default_manager.none()
-
-        model_table = qn(model._meta.db_table)
-        # This query selects the ids of all objects which have all the
-        # given tags.
-        query = """
-        SELECT %(model_pk)s
-        FROM %(model)s, %(tagged_item)s
-        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
-          AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
-          AND %(model_pk)s = %(tagged_item)s.object_id
-        GROUP BY %(model_pk)s
-        HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
-            'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
-            'model': model_table,
-            'tagged_item': qn(self.model._meta.db_table),
-            'content_type_id': ContentType.objects.get_for_model(model).pk,
-            'tag_id_placeholders': ','.join(['%s'] * tag_count),
-            'tag_count': tag_count,
-        }
-
-        cursor = connection.cursor()
-        cursor.execute(query, [tag.pk for tag in tags])
-        object_ids = [row[0] for row in cursor.fetchall()]
-        if len(object_ids) > 0:
-            return queryset.filter(pk__in=object_ids)
-        else:
-            return model._default_manager.none()
-
-    def get_union_by_model(self, queryset_or_model, tags):
-        """
-        Create a ``QuerySet`` containing instances of the specified
-        model associated with *any* of the given list of tags.
-        """
-        tags = self.tag_model.get_tag_list(tags)
-        tag_count = len(tags)
-        queryset, model = get_queryset_and_model(queryset_or_model)
-
-        if not tag_count:
-            return model._default_manager.none()
-
-        model_table = qn(model._meta.db_table)
-        # This query selects the ids of all objects which have any of
-        # the given tags.
-        query = """
-        SELECT %(model_pk)s
-        FROM %(model)s, %(tagged_item)s
-        WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
-          AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
-          AND %(model_pk)s = %(tagged_item)s.object_id
-        GROUP BY %(model_pk)s""" % {
-            'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
-            'model': model_table,
-            'tagged_item': qn(self.model._meta.db_table),
-            'content_type_id': ContentType.objects.get_for_model(model).pk,
-            'tag_id_placeholders': ','.join(['%s'] * tag_count),
-        }
-
-        cursor = connection.cursor()
-        cursor.execute(query, [tag.pk for tag in tags])
-        object_ids = [row[0] for row in cursor.fetchall()]
-        if len(object_ids) > 0:
-            return queryset.filter(pk__in=object_ids)
-        else:
-            return model._default_manager.none()
-
-    def get_related(self, obj, queryset_or_model, num=None):
-        """
-        Retrieve a list of instances of the specified model which share
-        tags with the model instance ``obj``, ordered by the number of
-        shared tags in descending order.
-
-        If ``num`` is given, a maximum of ``num`` instances will be
-        returned.
-        """
-        queryset, model = get_queryset_and_model(queryset_or_model)
-        model_table = qn(model._meta.db_table)
-        content_type = ContentType.objects.get_for_model(obj)
-        related_content_type = ContentType.objects.get_for_model(model)
-        query = """
-        SELECT %(model_pk)s, COUNT(related_tagged_item.object_id) AS %(count)s
-        FROM %(model)s, %(tagged_item)s, %(tag)s, %(tagged_item)s related_tagged_item
-        WHERE %(tagged_item)s.object_id = %%s
-          AND %(tagged_item)s.content_type_id = %(content_type_id)s
-          AND %(tag)s.id = %(tagged_item)s.tag_id
-          AND related_tagged_item.content_type_id = %(related_content_type_id)s
-          AND related_tagged_item.tag_id = %(tagged_item)s.tag_id
-          AND %(model_pk)s = related_tagged_item.object_id"""
-        if content_type.pk == related_content_type.pk:
-            # Exclude the given instance itself if determining related
-            # instances for the same model.
-            query += """
-          AND related_tagged_item.object_id != %(tagged_item)s.object_id"""
-        query += """
-        GROUP BY %(model_pk)s
-        ORDER BY %(count)s DESC
-        %(limit_offset)s"""
-        query = query % {
-            'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
-            'count': qn('count'),
-            'model': model_table,
-            'tagged_item': qn(self.model._meta.db_table),
-            'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table),
-            'content_type_id': content_type.pk,
-            'related_content_type_id': related_content_type.pk,
-            'limit_offset': num is not None and connection.ops.limit_offset_sql(num) or '',
-        }
-
-        cursor = connection.cursor()
-        cursor.execute(query, [obj.pk])
-        object_ids = [row[0] for row in cursor.fetchall()]
-        if len(object_ids) > 0:
-            # Use in_bulk here instead of an id__in lookup, because id__in would
-            # clobber the ordering.
-            object_dict = queryset.in_bulk(object_ids)
-            return [object_dict[object_id] for object_id in object_ids \
-                    if object_id in object_dict]
-        else:
-            return []
-
-
-##########
-# Models #
-##########
-def create_intermediary_table_model(model):
-    """Create an intermediary table model for the specific tag model"""
-    name = model.__name__ + 'Relation'
-     
-    class Meta:
-        db_table = '%s_relation' % model._meta.db_table
-        unique_together = (('tag', 'content_type', 'object_id'),)
-
-    def obj_unicode(self):
-        return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag)
-        
-    # Set up a dictionary to simulate declarations within a class    
-    attrs = {
-        '__module__': model.__module__,
-        'Meta': Meta,
-        'tag': models.ForeignKey(model, verbose_name=_('tag'), related_name='items'),
-        'content_type': models.ForeignKey(ContentType, verbose_name=_('content type')),
-        'object_id': models.PositiveIntegerField(_('object id'), db_index=True),
-        'content_object': generic.GenericForeignKey('content_type', 'object_id'),
-        '__unicode__': obj_unicode,
-    }
-
-    return type(name, (models.Model,), attrs)
-
-
-class TagMeta(ModelBase):
-    "Metaclass for tag models (models inheriting from TagBase)."
-    def __new__(cls, name, bases, attrs):
-        model = super(TagMeta, cls).__new__(cls, name, bases, attrs)
-        if not model._meta.abstract:
-            # Create an intermediary table and register custom managers for concrete models
-            model.intermediary_table_model = create_intermediary_table_model(model)
-            TagManager(model.intermediary_table_model).contribute_to_class(model, 'objects')
-            TaggedItemManager(model).contribute_to_class(model.intermediary_table_model, 'objects')
-        return model
-
-
-class TagBase(models.Model):
-    """Abstract class to be inherited by model classes."""
-    __metaclass__ = TagMeta
-    
-    class Meta:
-        abstract = True
-    
-    @staticmethod
-    def get_tag_list(tag_list):
-        """
-        Utility function for accepting tag input in a flexible manner.
-        
-        You should probably override this method in your subclass.
-        """
-        if isinstance(tag_list, TagBase):
-            return [tag_list]
-        else:
-            return tag_list
-
diff --git a/newtagging/views.py b/newtagging/views.py
deleted file mode 100644 (file)
index 150a084..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-Tagging related views.
-"""
-from django.http import Http404
-from django.utils.translation import ugettext as _
-from django.views.generic.list_detail import object_list
-
-
-def tagged_object_list(request, queryset_or_model=None, tag_model=None, tags=None,
-        related_tags=False, related_tag_counts=True, **kwargs):
-    """
-    A thin wrapper around
-    ``django.views.generic.list_detail.object_list`` which creates a
-    ``QuerySet`` containing instances of the given queryset or model
-    tagged with the given tag.
-
-    In addition to the context variables set up by ``object_list``, a
-    ``tag`` context variable will contain the ``Tag`` instance for the
-    tag.
-
-    If ``related_tags`` is ``True``, a ``related_tags`` context variable
-    will contain tags related to the given tag for the given model.
-    Additionally, if ``related_tag_counts`` is ``True``, each related
-    tag will have a ``count`` attribute indicating the number of items
-    which have it in addition to the given tag.
-    """
-    # Check attributes
-    if queryset_or_model is None:
-        raise AttributeError(_('tagged_object_list must be called with a queryset or a model.'))
-    if tag_model is None:
-        raise AttributeError(_('tagged_object_list must be called with a tag model.'))
-    if tags is None:
-        raise AttributeError(_('tagged_object_list must be called with a tag.'))
-
-    tag_instances = tag_model.get_tag_list(tags)
-    if tag_instances is None:
-        raise Http404(_('No tags found matching "%s".') % tags)
-    queryset = tag_model.intermediary_table_model.objects.get_intersection_by_model(queryset_or_model, tag_instances)
-    if not kwargs.has_key('extra_context'):
-        kwargs['extra_context'] = {}
-    kwargs['extra_context']['tags'] = tag_instances
-    if related_tags:
-        kwargs['extra_context']['related_tags'] = \
-            tag_model.objects.related_for_model(tag_instances, queryset_or_model,
-                                          counts=related_tag_counts)
-    return object_list(request, queryset, **kwargs)
-
diff --git a/pagination/__init__.py b/pagination/__init__.py
deleted file mode 100644 (file)
index 8b13789..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pagination/middleware.py b/pagination/middleware.py
deleted file mode 100644 (file)
index 0bab767..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-class PaginationMiddleware(object):
-    """
-    Inserts a variable representing the current page onto the request object if
-    it exists in either **GET** or **POST** portions of the request.
-    """
-    def process_request(self, request):
-        try:
-            request.page = int(request.REQUEST['page'])
-        except (KeyError, ValueError):
-            request.page = 1
\ No newline at end of file
diff --git a/pagination/models.py b/pagination/models.py
deleted file mode 100644 (file)
index 8b13789..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pagination/templates/pagination/pagination.html b/pagination/templates/pagination/pagination.html
deleted file mode 100644 (file)
index 3799314..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-{% if is_paginated %}
-<div class="pagination">
-    {% if page_obj.has_previous %}
-        <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">&lsaquo;&lsaquo; previous</a>
-    {% else %}
-        <span class="disabled prev">&lsaquo;&lsaquo; previous</span>
-    {% endif %}
-    {% for page in pages %}
-        {% if page %}
-            {% ifequal page page_obj.number %}
-                <span class="current page">{{ page }}</span>
-            {% else %}
-                <a href="?page={{ page }}{{ getvars }}" class="page">{{ page }}</a>
-            {% endifequal %}
-        {% else %}
-            ...
-        {% endif %}
-    {% endfor %}
-    {% if page_obj.has_next %}
-        <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="next">next &rsaquo;&rsaquo;</a>
-    {% else %}
-        <span class="disabled next">next &rsaquo;&rsaquo;</span>
-    {% endif %}
-</div>
-{% endif %}
diff --git a/pagination/templatetags/__init__.py b/pagination/templatetags/__init__.py
deleted file mode 100644 (file)
index 8b13789..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/pagination/templatetags/pagination_tags.py b/pagination/templatetags/pagination_tags.py
deleted file mode 100644 (file)
index 55e5392..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-try:
-    set
-except NameError:
-    from sets import Set as set
-from django import template
-from django.db.models.query import QuerySet
-from django.core.paginator import Paginator, QuerySetPaginator, InvalidPage
-
-register = template.Library()
-
-DEFAULT_PAGINATION = 20
-DEFAULT_WINDOW = 4
-DEFAULT_ORPHANS = 0
-
-def do_autopaginate(parser, token):
-    """
-    Splits the arguments to the autopaginate tag and formats them correctly.
-    """
-    split = token.split_contents()
-    if len(split) == 2:
-        return AutoPaginateNode(split[1])
-    elif len(split) == 3:
-        try:
-            paginate_by = int(split[2])
-        except ValueError:
-            raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
-        return AutoPaginateNode(split[1], paginate_by=paginate_by)
-    elif len(split) == 4:
-        try:
-            paginate_by = int(split[2])
-        except ValueError:
-            raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
-        try:
-            orphans = int(split[3])
-        except ValueError:
-            raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])           
-        return AutoPaginateNode(split[1], paginate_by=paginate_by, orphans=orphans)
-    else:
-        raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
-
-class AutoPaginateNode(template.Node):
-    """
-    Emits the required objects to allow for Digg-style pagination.
-    
-    First, it looks in the current context for the variable specified.  This
-    should be either a QuerySet or a list.
-    
-    1. If it is a QuerySet, this ``AutoPaginateNode`` will emit a 
-       ``QuerySetPaginator`` and the current page object into the context names
-       ``paginator`` and ``page_obj``, respectively.
-    
-    2. If it is a list, this ``AutoPaginateNode`` will emit a simple
-       ``Paginator`` and the current page object into the context names 
-       ``paginator`` and ``page_obj``, respectively.
-    
-    It will then replace the variable specified with only the objects for the
-    current page.
-    
-    .. note::
-        
-        It is recommended to use *{% paginate %}* after using the autopaginate
-        tag.  If you choose not to use *{% paginate %}*, make sure to display the
-        list of availabale pages, or else the application may seem to be buggy.
-    """
-    def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS):
-        self.queryset_var = template.Variable(queryset_var)
-        self.paginate_by = paginate_by
-        self.orphans = orphans
-
-    def render(self, context):
-        key = self.queryset_var.var
-        value = self.queryset_var.resolve(context)
-        if issubclass(value.__class__, QuerySet):
-            model = value.model
-            paginator_class = QuerySetPaginator
-        else:
-            value = list(value)
-            try:
-                model = value[0].__class__
-            except IndexError:
-                return u''
-            paginator_class = Paginator
-        paginator = paginator_class(value, self.paginate_by, self.orphans)
-        try:
-            page_obj = paginator.page(context['request'].page)
-        except InvalidPage:
-            context[key] = []
-            context['invalid_page'] = True
-            return u''
-        context[key] = page_obj.object_list
-        context['paginator'] = paginator
-        context['page_obj'] = page_obj
-        return u''
-
-def paginate(context, window=DEFAULT_WINDOW):
-    """
-    Renders the ``pagination/pagination.html`` template, resulting in a
-    Digg-like display of the available pages, given the current page.  If there
-    are too many pages to be displayed before and after the current page, then
-    elipses will be used to indicate the undisplayed gap between page numbers.
-    
-    Requires one argument, ``context``, which should be a dictionary-like data
-    structure and must contain the following keys:
-    
-    ``paginator``
-        A ``Paginator`` or ``QuerySetPaginator`` object.
-    
-    ``page_obj``
-        This should be the result of calling the page method on the 
-        aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
-        the current page.
-    
-    This same ``context`` dictionary-like data structure may also include:
-    
-    ``getvars``
-        A dictionary of all of the **GET** parameters in the current request.
-        This is useful to maintain certain types of state, even when requesting
-        a different page.
-        """
-    try:
-        paginator = context['paginator']
-        page_obj = context['page_obj']
-        page_range = paginator.page_range
-        # First and last are simply the first *n* pages and the last *n* pages,
-        # where *n* is the current window size.
-        first = set(page_range[:window])
-        last = set(page_range[-window:])
-        # Now we look around our current page, making sure that we don't wrap
-        # around.
-        current_start = page_obj.number-1-window
-        if current_start < 0:
-            current_start = 0
-        current_end = page_obj.number-1+window
-        if current_end < 0:
-            current_end = 0
-        current = set(page_range[current_start:current_end])
-        pages = []
-        # If there's no overlap between the first set of pages and the current
-        # set of pages, then there's a possible need for elusion.
-        if len(first.intersection(current)) == 0:
-            first_list = sorted(list(first))
-            second_list = sorted(list(current))
-            pages.extend(first_list)
-            diff = second_list[0] - first_list[-1]
-            # If there is a gap of two, between the last page of the first
-            # set and the first page of the current set, then we're missing a
-            # page.
-            if diff == 2:
-                pages.append(second_list[0] - 1)
-            # If the difference is just one, then there's nothing to be done,
-            # as the pages need no elusion and are correct.
-            elif diff == 1:
-                pass
-            # Otherwise, there's a bigger gap which needs to be signaled for
-            # elusion, by pushing a None value to the page list.
-            else:
-                pages.append(None)
-            pages.extend(second_list)
-        else:
-            pages.extend(sorted(list(first.union(current))))
-        # If there's no overlap between the current set of pages and the last
-        # set of pages, then there's a possible need for elusion.
-        if len(current.intersection(last)) == 0:
-            second_list = sorted(list(last))
-            diff = second_list[0] - pages[-1]
-            # If there is a gap of two, between the last page of the current
-            # set and the first page of the last set, then we're missing a 
-            # page.
-            if diff == 2:
-                pages.append(second_list[0] - 1)
-            # If the difference is just one, then there's nothing to be done,
-            # as the pages need no elusion and are correct.
-            elif diff == 1:
-                pass
-            # Otherwise, there's a bigger gap which needs to be signaled for
-            # elusion, by pushing a None value to the page list.
-            else:
-                pages.append(None)
-            pages.extend(second_list)
-        else:
-            pages.extend(sorted(list(last.difference(current))))
-        to_return = {
-            'pages': pages,
-            'page_obj': page_obj,
-            'paginator': paginator,
-            'is_paginated': paginator.count > paginator.per_page,
-        }
-        if 'request' in context:
-            getvars = context['request'].GET.copy()
-            if 'page' in getvars:
-                del getvars['page']
-            if len(getvars.keys()) > 0:
-                to_return['getvars'] = "&%s" % getvars.urlencode()
-            else:
-                to_return['getvars'] = ''
-        return to_return
-    except KeyError:
-        return {}
-register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
-register.tag('autopaginate', do_autopaginate)
\ No newline at end of file
diff --git a/pagination/tests.py b/pagination/tests.py
deleted file mode 100644 (file)
index 837e55c..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
->>> from django.core.paginator import Paginator
->>> from pagination.templatetags.pagination_tags import paginate
->>> from django.template import Template, Context
-
->>> p = Paginator(range(15), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, 5, 6, 7, 8]
-
->>> p = Paginator(range(17), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, 5, 6, 7, 8, 9]
-
->>> p = Paginator(range(19), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 7, 8, 9, 10]
-
->>> p = Paginator(range(21), 2)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 8, 9, 10, 11]
-
-# Testing orphans
->>> p = Paginator(range(5), 2, 1)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2]
-
->>> p = Paginator(range(21), 2, 1)
->>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages']
-[1, 2, 3, 4, None, 7, 8, 9, 10]
-
->>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}")
-
-# WARNING: Please, please nobody read this portion of the code!
->>> class GetProxy(object):
-...     def __iter__(self): yield self.__dict__.__iter__
-...     def copy(self): return self
-...     def urlencode(self): return u''
-...     def keys(self): return []
->>> class RequestProxy(object):
-...     page = 1
-...     GET = GetProxy()
->>>
-# ENDWARNING
-
->>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
-u'\\n<div class="pagination">...
->>>
->>> t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
-u'\\n<div class="pagination">...
->>>
-"""
\ No newline at end of file