Commits

Donald Stufft committed ef86eac

initial import of alpha

Comments (0)

Files changed (25)

+syntax: glob
+
+*.pyc
+*.swp
+The primary author of cmsplugin-markup is Donald von Stufft <donald@xenofox.com>
+==========================
+cmsplugin-markup changelog
+==========================
+
+Version 0.1, 30 Nov 2009:
+------------------------
+
+* First packaged version usting distutils.
+Thanks for downloading cmsplugin-markup.
+
+To install it, run the following command inside this directory:
+
+    python setup.py install
+
+If you have the Python ``easy_install`` utility available, you can also type the following to download and install in one step::
+
+    easy_install cmsplugin-markup
+
+Or if you're using ``pip``::
+
+    pip install cmsplugin-markup
+
+Or if you'd prefer you can simply place the included ``cmsplugin_markup`` directory somewhere on your Python path, or symlink to it from somewhere on your Python path; this is useful if your working from a Mercurial checkout.
+
+Note that this application requires Python2.3 or later, and a functional installation of Django 1.` or newer as well as a functional install of Django-CMS 2.` Final or newer. Each of the markup plugins also have their own dependencies.
+Copyright (c) 2009, Xenofox, LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+        this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+        this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+    * Neither the name of the Xenofox, LLC nor the names of its contributors 
+        may be used to endorse or promote products derived from this software
+        without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+include CHANGELOG
+include INSTALL
+include LICENSE
+include MANIFEST.in
+include README
+include AUTHORS
+recursive-include docs *
+
+========================
+Django CMS MarkUp
+========================
+
+This is a fairly simple plugin for django-cms that allows using various markup languages on a page. It impliments each of the various markup languages via a plugin based backend system. By default it comes with Markdown, ReST and Textile. It depends on Django 1.1 or newer and on Django-Cms 2.0 final or newer.
+
+For installation instructions, see the file "INSTALL" in this directory; for instructions on how to write your own backends, see the file "backends.rst" in the "docs/" directory.

cmsplugin_markup/__init__.py

+VERSION = (0,1,0,'alpha', 1)
+__version__ = '.'.join(map(str, VERSION))
+
+def get_version():
+    version = '%s.%s' % (VERSION[0], VERSION[1])
+    if VERSION[2]:
+        version = '%s.%s' % (version, VERSION[2])
+    if VERSION[3:] == ('alpha', 0):
+        version = '%s pre-alpha' % version
+    else:
+        if VERSION[3] != 'final':
+            version = '%s %s %s' % (version, VERSION[3], VERSION[4])
+    return version
+
+# patch settings
+try:
+    from conf import patch_settings
+    from django.conf import settings
+    patch_settings()
+except ImportError:
+    """
+    This exception means that either the application is being built, or is
+    otherwise installed improperly. Both make running patch_settings
+    irrelevant.
+    """
+    pass

cmsplugin_markup/cms_plugins.py

+from django.utils.translation import ugettext as _
+from cms.plugin_base import CMSPluginBase
+from cms.plugin_pool import plugin_pool
+
+from cmsplugin_markup.models import MarkupField
+from cmsplugin_markup.forms import MarkupForm
+
+class MarkupPlugin(CMSPluginBase):
+    model = MarkupField
+    name = _('Markup')
+    form = MarkupForm
+    render_template = 'cmsplugin_markup/markup.html'
+
+    def render(self, context, instance, placeholder):
+        context.update({
+            'object': instance,
+            'placeholder': placeholder,
+            })
+        return context
+
+plugin_pool.register_plugin(MarkupPlugin)

cmsplugin_markup/conf/__init__.py

+from django.conf import settings
+#from patch import post_patch_check
+
+ALREADY_PATCHED = False
+
+def patch_settings():
+    """Merge settings with global cms settings, so all required attributes
+    will exist. Never override, just append non existing settings.
+
+    Also check for settings inconstistence if settings.DEBUG
+    """
+    global ALREADY_PATCHED
+
+    if ALREADY_PATCHED:
+        return
+
+    ALREADY_PATCHED = True
+
+    from cmsplugin_markup.conf import global_settings
+    
+    # merge with global cms settings
+    for attr in dir(global_settings):
+        if attr == attr.upper() and not hasattr(settings, attr):
+            setattr(settings._wrapped, attr, getattr(global_settings, attr))

cmsplugin_markup/conf/global_settings.py

+"""
+Global cmsplugin_markup settings, are applied if there isn't a value defined
+in project settings. All available settings are listed here. Please don't 
+put any functions / test inside, if you need to create some dynamic values /
+tests, take a look at cmsplugin_markup.conf.patch
+"""
+import os
+
+from django.conf import settings
+
+CMS_MARKUP_OPTIONS = (
+        'cmsplugin_markup.plugins.markdown',
+        'cmsplugin_markup.plugins.textile',
+        'cmsplugin_markup.plugins.restructuredtext',
+)

cmsplugin_markup/forms.py

+from django.forms.models import ModelForm
+from django import forms
+
+from cmsplugin_markup.models import MarkupField
+
+class MarkupForm(ModelForm):
+    class Meta:
+        model = MarkupField
+        exclude = ('body_html',)

cmsplugin_markup/models.py

+from django.db import models
+from django.utils.translation import ugettext as _
+from django.utils.html import strip_tags
+from django.utils.text import truncate_words
+from django.conf import settings
+from cms.models import CMSPlugin
+
+from cmsplugin_markup import utils
+
+MARKUP_CHOICES = utils.compile_markup_choices(settings.CMS_MARKUP_OPTIONS)
+
+class MarkupField(CMSPlugin):
+    body = models.TextField(_('Body'))
+    body_html = models.TextField(blank=True)
+    markup = models.CharField(
+            _('Markup'),
+            max_length=20,
+            choices=MARKUP_CHOICES
+            )
+
+    def __unicode__(self):
+        return u'%s' %(truncate_words(strip_tags(self.body_html), 3)[:30]+'...')
+
+    def save(self, *args, **kwargs):
+        self.body_html = utils.markup_parser(self.body, self.markup)
+        return super(MarkupField, self).save(*args, **kwargs)

cmsplugin_markup/plugins/__init__.py

Empty file added.

cmsplugin_markup/plugins/markdown/__init__.py

+from cmsplugin_markup.plugins.markdown.mark_down import Markup
+
+# patch settings
+try:
+    from conf import patch_settings
+    from django.conf import settings
+    patch_settings()
+except ImportError:
+    """
+    This exception means that either the application is being built, or is
+    otherwise installed improperly. Both make running patch_settings
+    irrelevant.
+    """
+    pass

cmsplugin_markup/plugins/markdown/conf/__init__.py

+from django.conf import settings
+
+ALREADY_PATCHED = False
+
+def patch_settings():
+    """Merge settings with global cms settings, so all required attributes
+    will exist. Never override, just append non existing settings.
+
+    Also check for settings inconstistence if settings.DEBUG
+    """
+    global ALREADY_PATCHED
+
+    if ALREADY_PATCHED:
+        return
+
+    ALREADY_PATCHED = True
+
+    from cmsplugin_markup.plugins.markdown.conf import global_settings
+    
+    # merge with global cms settings
+    for attr in dir(global_settings):
+        if attr == attr.upper() and not hasattr(settings, attr):
+            setattr(settings._wrapped, attr, getattr(global_settings, attr))

cmsplugin_markup/plugins/markdown/conf/global_settings.py

+"""
+Global cmsplugin_markup settings, are applied if there isn't a value defined
+in project settings. All available settings are listed here. Please don't 
+put any functions / test inside, if you need to create some dynamic values /
+tests, take a look at cmsplugin_markup.conf.patch
+"""
+import os
+
+from django.conf import settings
+
+CMS_MARKDOWN_EXTENSIONS = []

cmsplugin_markup/plugins/markdown/mark_down.py

+from django.conf import settings
+from django.utils.encoding import smart_str, force_unicode
+
+class Markup(object):
+
+    name = 'Markdown'
+    identifier = 'markdown'
+
+    def parse(self, value):
+        try:
+            import markdown
+        except ImportError:
+            return force_unicode(value)
+        else:
+            if hasattr(markdown, 'version'):
+                extensions = settings.CMS_MARKDOWN_EXTENSIONS
+                if len(extensions) > 0 and extensions[0] == "safe":
+                    extensions = extensions[1:]
+                    safe_mode = True
+                else:
+                    safe_mode = False
+
+                if getattr(markdown, 'version_info', None) < (1,7):
+                    return force_unicode(markdown.markdown(smart_str(value),
+                                extensions, safe_mode=safe_mode))
+                else:
+                    return markdown.markdown(force_unicode(value), extensions,
+                                safe_mode=safe_mode)
+            else:
+                return force_unicode(markdown.markdown(smart_str(value)))

cmsplugin_markup/plugins/restructuredtext/__init__.py

+from django.conf import settings
+from django.utils.encoding import smart_str, force_unicode
+
+class Markup(object):
+
+    name = 'ReST (ReStructured Text)'
+    identifier = 'restructuredtext'
+
+    def parse(self, value):
+        try:
+            from docutils.core import publish_parts
+        except ImportError:
+            return force_unicode(value)
+        else:
+            docutils_settings = getattr(settings,
+                    'RESTRUCTUREDTEXT_FILTER_SETTINGS', {})
+            parts = publish_parts(source=smart_str(value),
+                    writer_name="html4css1",
+                    settings_overrides=docutils_settings)
+            return force_unicode(parts["fragment"])

cmsplugin_markup/plugins/textile/__init__.py

+from django.conf import settings
+from django.utils.encoding import smart_str, force_unicode
+
+class Markup(object):
+
+    name = 'Textile'
+    identifier = 'textile'
+
+    def parse(self, value):
+        try:
+            import textile
+        except ImportError:
+            return force_unicode(value)
+        else:
+            return force_unicode(textile.textile(smart_str(value),
+                encoding='utf-8', output='utf-8'))

cmsplugin_markup/templates/cmsplugin_markup/markup.html

+{{ object.body_html|safe }}

cmsplugin_markup/utils/__init__.py

+from cmsplugin_markup.utils.markup import compile_markup_choices
+from cmsplugin_markup.utils.markup import get_list_of_markup_objects
+from cmsplugin_markup.utils.markup import markup_parser

cmsplugin_markup/utils/markup.py

+
+def get_list_of_markup_objects(markup_options):
+    """
+    Takes a tuple of python packages that impliment the cmsplugin_markup
+    api and return a dict of the objects with identifier as key.
+    """
+    import sys
+
+    objects = {}
+
+    for markup in markup_options:
+        try:
+            __import__(markup)
+            module = sys.modules[markup]
+        except ImportError:
+            continue
+
+        try:
+            # Check for required attributes
+            if not hasattr(module.Markup, 'name'):
+                continue
+            if not hasattr(module.Markup, 'identifier'):
+                continue
+            if not hasattr(module.Markup, 'parse'):
+                continue
+
+            objects[module.Markup.identifier] = module.Markup
+        except AttributeError:
+            continue
+    return objects
+
+def compile_markup_choices(markup_options):
+    """
+    Takes a tuple of python packages that impliment the cmsplugin_markup
+    api and makes a tuple of options for the forms.
+    """
+
+    choices = []
+    objects = get_list_of_markup_objects(markup_options)
+
+    for identifier, markup_object in objects.iteritems():
+        choices.append((identifier, markup_object.name))
+
+    return tuple(choices)
+
+def markup_parser(value, parser_identifier):
+    """
+    Takes a string and a parser identifier and returns a string parsed
+    by that parser. If anything goes wrong it returns the original string
+    """
+    from django.conf import settings
+
+    markup_objects = get_list_of_markup_objects(settings.CMS_MARKUP_OPTIONS)
+    obj = markup_objects[parser_identifier]()
+
+    return markup_objects[parser_identifier]().parse(value)

docs/backends.rst

+.. _backend-api:
+
+cmsplugin-markup backends
+=========================
+
+At its core, cmsplugin-markup is built around the idea of pluggable backends which can impliment different markup implimentations for parsing user input. By default it comes with Markdown, Textile and ReST Backends available in the cmsplugin_markup/plugins/ directory.
+
+Specifiying available backends
+------------------------------
+To determine which MarkUp options to give to the user, cmsplugin-markup looks for a setting called CMS_MARKUP_OPTIONS. It expects this to be a tuple in this format::
+
+    CMS_MARKUP_OPTIONS = (
+        'cmsplugin_markup.plugins.markdown',
+        'cmsplugin_markup.plugins.textile',
+        'cmsplugin_markup.plugins.restructuredtext',
+        )
+
+Each entry should be a string and should be a complete path to a python package that contains the required attributes.
+
+Backend API
+-----------
+
+To be used as a MarkUp backend, a python package must be laid out in a specific fashion and contain a class that must implement the following methods and variables. This class must be named Markup. An example class is below.::
+
+    class Markup(object):
+        name = 'Human Readable Name for the Mark Up'
+        identifier = 'Internal Identifier for Mark Up'
+
+        def parse(self, value):
+            return value
+
+This barebones class contains all the required pieces to work. 
+
+The ``name`` variable is a human readable name and may be any length. This is the name that will be presented to the user as the option. 
+
+The ``identifier`` variable is stored as a CharField and anything that is allowed in a CharField is allowed in this. It must be unique across all the installed MarkUp Parsers and may be at most 20 characters long. 
+
+The ``parse`` function must accept self, and a value argument. This function is where you will impliment the actual parsing of the user's input. At this point in time this function should fail silently and simply return an unchanged string. This might change in the future.
+
+Directory Layout
+~~~~~~~~~~~~~~~~
+
+A MarkUp Backend Package should be laid out as follows
+
+backend_name/
+- __init__.py
+- optional_class.py
+
+The __init__.py file should either contain the Markup class definition, or should import it from an optional python file in the same directory. The optional file may be named anything.
+#!/usr/bin/env python
+PACKAGE_NAME = 'cmsplugin_markup'
+PACKAGE_DIR = PACKAGE_NAME
+
+import os, sys
+
+from distutils.core import setup
+from distutils.command.install import INSTALL_SCHEMES
+
+def fullsplit(path, result=None):
+    """
+    Split a pathname into compontents (the opposite of os.path.join) in a
+    platform-neutral way.
+    """
+    if result is None:
+        result = []
+    head, tail = os.path.split(path)
+    if head == '':
+        return [tail] + result
+    if head == path:
+        return result
+    return fullsplit(head, [tail] + result)
+
+# Tell disutils to put the data_files in platofmr-specific installation
+# locations.
+for scheme in INSTALL_SCHEMES.values():
+    scheme['data'] = scheme['purelib']
+
+# Compile the list of packages available, because distuils doesn't have
+# and easy way to do this.
+packages, data_files = [], []
+root_dir = os.path.dirname(__file__)
+if root_dir != '':
+    os.chdir(root_dir)
+
+for dirpath, dirnames, filenames in os.walk(PACKAGE_DIR):
+    # Ignore dirnames that start with '.'
+    for i, dirname in enumerate(dirnames):
+        if dirname.startswith('.'): del dirnames[i]
+    if '__init__.py' in filenames:
+        packages.append('.'.join(fullsplit(dirpath)))
+    elif filenames:
+        data_files.append(
+                [dirpath, [os.path.join(dirpath, f) for f in filenames]]
+                )
+# Small hack for working with bdist_wininst
+# See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html
+if len(sys.argv) > 1 and sys.argv[1] == 'bdist_wininst':
+    for file_info in data_files:
+        file_info[0] = '\\PURELIB\\%s' % file_info[0]
+
+# Dynamically calculate the version based on package.VERSION
+version = __import__(PACKAGE_NAME).get_version()
+
+setup(
+        name='cmsplugin-markup',
+        version=version.replace(' ', '-'),
+        description='Adds a plugin based MarkUp System for django-cms',
+        author='Xenofox, LLC',
+        author_email='info@xenofox.com',
+        url='http://bitbucket.org/xenofox/cmsplugin-markup',
+        packages=packages,
+        data_files=data_files,
+        classifiers=[
+            'Development Status :: 3 - Alpha',
+            'Environment :: Web Environment',
+            'Framework :: Django',
+            'Intended Audience :: Developers',
+            'Natural Language :: English',
+            'Operating System :: OS Independent',
+            'Programming Language :: Python :: 2',
+            'Topic :: Software Development :: Libraries :: Python Modules',
+            'Topic :: Utilities',
+            'License :: OSI Approved :: BSD License',
+            ]
+        )