Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

multi-markup, internationalized models, post_published signal #16

Merged
merged 14 commits into from

2 participants

@rizumu
Collaborator

See: https://groups.google.com/group/pinax-core-dev/browse_thread/thread/c46e029dd7ca0bba

Changes:

  • python utf-8 shebang was missing the hypen, causing a warning in emacs http://www.evanjones.ca/python-utf8.html
  • added multi-markup support: HTML, Creole, Markdown, reStructuredText, & Textile and require them all in setup.py
  • Internationalize biblia models
  • add a post_published signal
  • add missing sites field from admin.py for posts
Thomas Schre... and others added some commits
@paltman paltman merged commit bc64605 into pinax:biblia
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 2, 2012
Commits on Jan 3, 2012
  1. Internationalize biblia models

    Thomas Schreiber authored
    * Remove 'null' from subtitle char field. Char fields can't be null.
    * Added internationalization ready verbose names for all fields and models.
    * Marked any other model strings for translation.
  2. remove underscores from verbose_names

    Thomas Schreiber authored
  3. Use django.utils.formats for the datetime objects

    Thomas Schreiber authored
  4. it is setuptools that has find_packages not distutils

    Thomas Schreiber authored
Commits on Jan 4, 2012
  1. fix typo

    Thomas Schreiber authored
  2. BIBLION_PARSER is no longer a global

    Thomas Schreiber authored
  3. update readme to refect multi-markup

    Thomas Schreiber authored
  4. fix typo

    Thomas Schreiber authored
  5. add a post_published signal

    Thomas Schreiber authored
  6. sites field was missing from the admin

    Thomas Schreiber authored
Commits on Jan 5, 2012
  1. fix variable referenced before assignment

    Thomas Schreiber authored
Commits on Jan 24, 2013
  1. @rizumu
This page is out of date. Refresh to see the latest.
View
2  README.rst
@@ -11,7 +11,7 @@ internal Pinax blog app once we've made it feature complete.
Current features include:
* support for multiple channels (e.g. technical vs business)
- * use of Creole as markup format
+ * use HTML, Creole, Markdown, reStructuredText or Textile as markup formats
* Atom feeds
* previewing of blog posts before publishing
* optional ability to announce new posts on twitter
View
6 biblion/admin.py
@@ -4,7 +4,7 @@
from biblion.models import Biblion, Post, Image
from biblion.forms import AdminPostForm
-from biblion.utils import can_tweet
+from biblion.utils.twitter import can_tweet
class ImageInline(admin.TabularInline):
@@ -18,7 +18,7 @@ class BiblionAdmin(admin.ModelAdmin):
}
-class PostAdmin(admin.ModelAdmin):
+class PostAdmin(admin.ModelAdmin):
list_display = ["biblion", "title", "published_flag"]
list_filter = ["biblion"]
form = AdminPostForm
@@ -27,10 +27,12 @@ class PostAdmin(admin.ModelAdmin):
"title",
"slug",
"author",
+ "markup_type",
"teaser",
"content",
"publish",
"publish_date",
+ "sites",
]
if can_tweet():
fields.append("tweet")
View
40 biblion/forms.py
@@ -3,14 +3,14 @@
from django import forms
from django.core.exceptions import ImproperlyConfigured
-from django.utils.functional import curry
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from biblion.models import Biblion, Post, Revision, Image
-from biblion.settings import PARSER
-from biblion.utils import can_tweet, load_path_attr, slugify_unique
+from biblion.signals import post_published
+from biblion.utils.twitter import can_tweet
+from biblion.utils.slugify import slugify_unique
class BiblionForm(forms.ModelForm):
@@ -131,14 +131,12 @@ def save(self):
if Post.objects.filter(pk=post.pk, published=None).count():
if self.cleaned_data["publish"]:
post.published = datetime.datetime.now()
-
+
if self.cleaned_data["publish_date"]:
post.published = self.cleaned_data["publish_date"]
-
- render_func = curry(load_path_attr(PARSER[0]), **PARSER[1])
- post.teaser_html = render_func(self.cleaned_data["teaser"])
- post.content_html = render_func(self.cleaned_data["content"])
+ post.teaser = self.cleaned_data["teaser"]
+ post.content = self.cleaned_data["content"]
post.updated = datetime.datetime.now()
post.save()
@@ -211,6 +209,7 @@ def __init__(self, *args, **kwargs):
if latest_revision:
# set initial data from the latest revision
+ self.fields["markup_type"].initial = latest_revision.markup_type
self.fields["teaser"].initial = latest_revision.teaser
self.fields["content"].initial = latest_revision.content
@@ -221,28 +220,29 @@ def __init__(self, *args, **kwargs):
def save(self):
post = super(AdminPostForm, self).save(commit=False)
-
- if post.pk is None:
- if self.cleaned_data["publish"]:
- post.published = datetime.datetime.now()
+ # only publish the first time publish has been checked
+ if (post.pk is None or Post.objects.filter(pk=post.pk, published=None).count()) and self.cleaned_data["publish"]:
+ post.published = datetime.datetime.now()
+ send_published_signal = True
else:
- if Post.objects.filter(pk=post.pk, published=None).count():
- if self.cleaned_data["publish"]:
- post.published = datetime.datetime.now()
-
+ send_published_signal = False
+
if self.cleaned_data["publish_date"]:
post.published = self.cleaned_data["publish_date"]
-
- render_func = curry(load_path_attr(PARSER[0]), **PARSER[1])
- post.teaser_html = render_func(self.cleaned_data["teaser"])
- post.content_html = render_func(self.cleaned_data["content"])
+ post.markup_type = self.cleaned_data["markup_type"]
+ post.teaser = self.cleaned_data["teaser"]
+ post.content = self.cleaned_data["content"]
post.updated = datetime.datetime.now()
post.save()
+ if send_published_signal:
+ post_published.send(sender=self, pk=post.pk)
+
r = Revision()
r.post = post
r.title = post.title
+ r.markup_type = self.cleaned_data["markup_type"]
r.teaser = self.cleaned_data["teaser"]
r.content = self.cleaned_data["content"]
r.author = post.author
View
5 biblion/markdown_parser.py
@@ -1,5 +0,0 @@
-import markdown
-
-
-def parse(text):
- return markdown.markdown(text)
View
119 biblion/models.py
@@ -1,4 +1,4 @@
-# -*- coding: utf8 -*-
+# -*- coding: utf-8 -*-
import urllib2
from datetime import datetime
@@ -7,7 +7,9 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.core.urlresolvers import reverse
+from django.utils import formats
from django.utils import simplejson as json
+from django.utils.translation import ugettext as _
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
@@ -18,17 +20,21 @@
twitter = None
from biblion.managers import PostManager
-from biblion.utils import can_tweet
+from biblion.utils.twitter import can_tweet
class Biblion(models.Model):
- title = models.CharField(max_length=128)
- subtitle = models.CharField(max_length=256, null=True, blank=True)
- slug = models.SlugField(unique=True)
- description = models.TextField()
- logo = models.FileField(upload_to="biblion_biblion_logo")
- sites = models.ManyToManyField(Site)
+ title = models.CharField(_("title"), max_length=128)
+ subtitle = models.CharField(_("subtitle"), max_length=256, blank=True)
+ slug = models.SlugField(_("slug"), unique=True)
+ description = models.TextField(_("description"))
+ logo = models.FileField(_("logo"), upload_to="biblion_biblion_logo")
+ sites = models.ManyToManyField(Site, verbose_name=_("list of sites"))
+
+ class Meta:
+ verbose_name = _("biblion")
+ verbose_name_plural = _("biblia")
def __unicode__(self):
return unicode(self.title)
@@ -39,30 +45,44 @@ def get_absolute_url(self):
class BiblionContributor(models.Model):
- biblion = models.ForeignKey(Biblion)
- user = models.ForeignKey(User)
- role = models.CharField(max_length=25)
+ biblion = models.ForeignKey(Biblion, verbose_name=_("biblion"))
+ user = models.ForeignKey(User, related_name="contributors", verbose_name=_("user"))
+ role = models.CharField(_("role"), max_length=25)
+
+ class Meta:
+ verbose_name = _("biblion contributor")
+ verbose_name_plural = _("biblion contributors")
class Post(models.Model):
- biblion = models.ForeignKey(Biblion, related_name="posts")
- sites = models.ManyToManyField(Site)
+ biblion = models.ForeignKey(Biblion, related_name="posts", verbose_name=_("biblion"))
+ sites = models.ManyToManyField(Site, verbose_name=_("list of sites"))
+
+ title = models.CharField(_("title"), max_length=90)
+ slug = models.SlugField(_("slug"))
+ author = models.ForeignKey(User, related_name="posts", verbose_name=_("author"))
- title = models.CharField(max_length=90)
- slug = models.SlugField()
- author = models.ForeignKey(User, related_name="posts")
+ markup_types = [
+ _("HTML"),
+ _("Creole"),
+ _("Markdown"),
+ _("reStructuredText"),
+ _("Textile"),
+ ]
+ MARKUP_CHOICES = zip(range(1, 1 + len(markup_types)), markup_types)
+ markup_type = models.IntegerField(_("markup type"), choices=MARKUP_CHOICES, default=1)
- teaser_html = models.TextField(editable=False)
- content_html = models.TextField(editable=False)
+ teaser = models.TextField(_("teaser"), editable=False)
+ content = models.TextField(_("content"), editable=False)
- tweet_text = models.CharField(max_length=140, editable=False)
+ tweet_text = models.CharField(_("tweet text"), max_length=140, editable=False)
- created = models.DateTimeField(default=datetime.now, editable=False) # when first revision was created
- updated = models.DateTimeField(null=True, blank=True, editable=False) # when last revision was create (even if not published)
- published = models.DateTimeField(null=True, blank=True, editable=False) # when last published
+ created = models.DateTimeField(_("created"), default=datetime.now, editable=False) # when first revision was created
+ updated = models.DateTimeField(_("updated"), null=True, blank=True, editable=False) # when last revision was created (even if not published)
+ published = models.DateTimeField(_("published"), null=True, blank=True, editable=False) # when last published
- view_count = models.IntegerField(default=0, editable=False)
+ view_count = models.IntegerField(_("view count"), default=0, editable=False)
def rev(self, rev_id):
return self.revisions.get(pk=rev_id)
@@ -79,6 +99,8 @@ def latest(self):
return None
class Meta:
+ verbose_name = _("post")
+ verbose_name_plural = _("posts")
ordering = ("-published",)
get_latest_by = "published"
@@ -107,13 +129,13 @@ def as_tweet(self):
def tweet(self):
if can_tweet():
account = twitter.Api(
- username = settings.TWITTER_USERNAME,
- password = settings.TWITTER_PASSWORD,
+ username=settings.TWITTER_USERNAME,
+ password=settings.TWITTER_PASSWORD,
)
account.PostUpdate(self.as_tweet())
else:
- raise ImproperlyConfigured("Unable to send tweet due to either "
- "missing python-twitter or required settings.")
+ raise ImproperlyConfigured(_("Unable to send tweet due to either \
+ missing python-twitter or required settings."))
def save(self, **kwargs):
self.updated_at = datetime.now()
@@ -147,22 +169,29 @@ def inc_views(self):
class Revision(models.Model):
- post = models.ForeignKey(Post, related_name="revisions")
+ post = models.ForeignKey(Post, related_name="revisions", verbose_name=_("post"))
- title = models.CharField(max_length=90)
- teaser = models.TextField()
+ title = models.CharField(_("title"), max_length=90)
- content = models.TextField()
+ markup_type = models.IntegerField(_("markup type"))
+ teaser = models.TextField(_("teaser"))
+ content = models.TextField(_("content"))
- author = models.ForeignKey(User, related_name="post_revisions")
+ author = models.ForeignKey(User, related_name="post revisions", verbose_name=_("author"))
- updated = models.DateTimeField(default=datetime.now)
- published = models.DateTimeField(null=True, blank=True)
+ updated = models.DateTimeField(_("updated"), default=datetime.now)
+ published = models.DateTimeField(_("published"), null=True, blank=True)
- view_count = models.IntegerField(default=0, editable=False)
+ view_count = models.IntegerField(_("view count"), default=0, editable=False)
+
+ class Meta:
+ verbose_name = _("revision")
+ verbose_name_plural = _("revisions")
def __unicode__(self):
- return 'Revision %s for %s' % (self.updated.strftime('%Y%m%d-%H%M'), self.post.slug)
+ return _("Revision %(datetime)s for %(slug)s") % {
+ "datetime": formats.localize("%Y%m%d-%H%M"),
+ "slug": self.post.slug}
def inc_views(self):
self.view_count += 1
@@ -173,19 +202,23 @@ class Image(models.Model):
post = models.ForeignKey(Post, related_name="images", blank=True, null=True)
- image_path = models.ImageField(upload_to="images/%Y/%m/%d")
- url = models.CharField(max_length=150, blank=True)
+ image_path = models.ImageField(_("image path"), upload_to="images/%Y/%m/%d")
+ url = models.CharField(_("url"), max_length=150, blank=True)
- timestamp = models.DateTimeField(default=datetime.now, editable=False)
+ timestamp = models.DateTimeField(_("timestamp"), default=datetime.now, editable=False)
+
+ class Meta:
+ verbose_name = _("image")
+ verbose_name_plural = _("images")
def __unicode__(self):
if self.pk is not None:
- return "{{ %d }}" % self.pk
+ return u"{{ %d }}" % self.pk
else:
- return "deleted image"
+ return _("deleted image")
class FeedHit(models.Model):
- request_data = models.TextField()
- created = models.DateTimeField(default=datetime.now)
+ request_data = models.TextField(_("request data"))
+ created = models.DateTimeField(_("created"), default=datetime.now)
View
4 biblion/settings.py
@@ -1,4 +0,0 @@
-from django.conf import settings
-
-
-PARSER = getattr(settings, "BIBLION_PARSER", ["biblion.creole_parser.parse", {}])
View
4 biblion/signals.py
@@ -0,0 +1,4 @@
+from django.dispatch import Signal
+
+
+post_published = Signal(providing_args=["pk"])
View
11 biblion/templates/biblion/atom_entry.xml
@@ -1,3 +1,6 @@
+{% load biblion_tags %}
+
+
<entry xml:base="http://{{ current_site.domain }}/">
<id>http://{{ current_site.domain }}{{ entry.get_absolute_url }}</id>
<title>{{ entry.title }}</title>
@@ -12,14 +15,14 @@
<summary type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
- {{ entry.teaser_html|safe }}
+ {{ entry.teaser|to_html }}
</div>
</summary>
<content type="xhtml" xml:lang="en">
<div xmlns="http://www.w3.org/1999/xhtml">
- {{ entry.teaser_html|safe }}
- {{ entry.content_html|safe }}
+ {{ entry.teaser|to_html }}
+ {{ entry.content|to_html }}
</div>
</content>
-</entry>
+</entry>
View
0  biblion/templatetags/__init__.py
No changes.
View
19 biblion/templatetags/biblion_tags.py
@@ -0,0 +1,19 @@
+from django import template
+
+from biblion.utils.code_hilite import to_html
+
+
+register = template.Library()
+
+
+register.filter("to_html", to_html)
+
+
+def show_post_brief(context, post):
+ return {
+ "post": post,
+ "last": context["forloop"]["last"],
+ "can_edit": context["user"].is_staff,
+ }
+
+register.inclusion_tag("blog/post_brief.html", takes_context=True)(show_post_brief)
View
45 biblion/utils.py
@@ -1,45 +0,0 @@
-from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
-from django.template.defaultfilters import slugify
-try:
- from django.utils.importlib import import_module
-except ImportError:
- from importlib import import_module
-
-try:
- import twitter
-except ImportError:
- twitter = None
-
-
-def can_tweet():
- creds_available = (
- hasattr(settings, "TWITTER_USERNAME") and hasattr(settings, "TWITTER_PASSWORD")
- )
- return twitter and creds_available
-
-
-def slugify_unique(value, model, slugfield="slug"):
- suffix = 0
- potential = base = slugify(value)
- while True:
- if suffix:
- potential = "-".join([base, str(suffix)])
- if not model.objects.filter(**{slugfield: potential}).count():
- print model.objects.filter(**{slugfield: potential})
- return potential
- suffix += 1
-
-
-def load_path_attr(path):
- i = path.rfind(".")
- module, attr = path[:i], path[i+1:]
- try:
- mod = import_module(module)
- except ImportError, e:
- raise ImproperlyConfigured("Error importing %s: '%s'" % (module, e))
- try:
- attr = getattr(mod, attr)
- except AttributeError:
- raise ImproperlyConfigured("Module '%s' does not define a '%s'" % (module, attr))
- return attr
View
0  biblion/utils/__init__.py
No changes.
View
97 biblion/utils/code_hilite.py
@@ -0,0 +1,97 @@
+from pygments import highlight
+from pygments.formatters import HtmlFormatter
+from pygments.lexers import get_lexer_by_name, TextLexer
+
+from docutils import nodes
+from docutils.writers import html4css1
+from docutils.core import publish_parts
+from docutils.parsers.rst import directives
+
+import markdown
+import textile
+
+from django.utils.safestring import mark_safe
+
+from biblion.utils.mdx_codehilite import makeExtension
+from biblion.utils import creole_parser
+
+
+VARIANTS = {}
+
+
+def pygments_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ try:
+ lexer = get_lexer_by_name(arguments[0])
+ except (ValueError, IndexError):
+ # no lexer found - use the text one instead of an exception
+ lexer = TextLexer()
+ parsed = highlight(u"\n".join(content), lexer, HtmlFormatter())
+ return [nodes.raw("", parsed, format="html")]
+pygments_directive.arguments = (0, 1, False)
+pygments_directive.content = 1
+pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
+
+directives.register_directive("sourcecode", pygments_directive)
+
+
+class HTMLWriter(html4css1.Writer):
+ def __init__(self):
+ html4css1.Writer.__init__(self)
+ self.translator_class = HTMLTranslator
+
+
+class HTMLTranslator(html4css1.HTMLTranslator):
+ named_tags = []
+
+ def visit_literal(self, node):
+ # @@@ wrapping fixes.
+ self.body.append("<code>%s</code>" % node.astext())
+ raise nodes.SkipNode
+
+
+def rst_to_html(value):
+ parts = publish_parts(source=value, writer=HTMLWriter(),
+ settings_overrides={"initial_header_level": 2})
+ return parts["fragment"]
+
+
+def markdown_to_html(text):
+ """
+ Convert markdown to HTML with code hiliting
+ """
+ return unicode(markdown.markdown(text, extensions=('codehilite',)))
+
+
+def textile_to_html(text):
+ """
+ Convert textile to HTML
+ @@@ add code hiliting support
+ """
+ return unicode(textile.textile(text))
+
+
+def creole_to_html(text):
+ """
+ Convert creole to HTML
+ @@@ add code hiliting support
+ """
+ return unicode(creole_parser.parse(text, emitter=creole_parser.BiblionHtmlEmitter))
+
+
+def to_html(obj):
+ """
+ Markup filter that converts an object to html formatting.
+ Syntax hiliting support for rst and markdown only.
+ """
+ if obj.markup_type == 1: # HTML
+ html = obj.content
+ elif obj.markup_type == 2: # Creole
+ html = creole_to_html(obj.content)
+ elif obj.markup_type == 3: # Markdown
+ html = markdown_to_html(obj.content)
+ elif obj.markup_type == 4: # reStructuredText
+ html = rst_to_html(obj.content)
+ elif obj.markup_type == 5: # Textile
+ html = textile_to_html(obj.content)
+ return mark_safe(html)
View
55 biblion/creole_parser.py → biblion/utils/creole_parser.py
@@ -11,12 +11,13 @@
class Rules:
# For the link targets:
- proto = r'http|https|ftp|nntp|news|mailto|telnet|file|irc'
- extern = r'(?P<extern_addr>(?P<extern_proto>%s):.*)' % proto
- interwiki = r'''
+ proto = r"http|https|ftp|nntp|news|mailto|telnet|file|irc"
+ extern = r"(?P<extern_addr>(?P<extern_proto>%s):.*)" % proto
+ interwiki = r"""
(?P<inter_wiki> [A-Z][a-zA-Z]+ ) :
(?P<inter_page> .* )
- '''
+ """
+
class HtmlEmitter(object):
"""
@@ -24,10 +25,10 @@ class HtmlEmitter(object):
tree consisting of DocNodes.
"""
- addr_re = re.compile('|'.join([
+ addr_re = re.compile("|".join([
Rules.extern,
Rules.interwiki,
- ]), re.X | re.U) # for addresses
+ ]), re.X | re.U) # for addresses
def __init__(self, root):
self.root = root
@@ -35,15 +36,15 @@ def __init__(self, root):
def get_text(self, node):
"""Try to emit whatever text is in the node."""
try:
- return node.children[0].content or ''
+ return node.children[0].content or ""
except:
- return node.content or ''
+ return node.content or ""
def html_escape(self, text):
- return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
def attr_escape(self, text):
- return self.html_escape(text).replace('"', '&quot')
+ return self.html_escape(text).replace('"', "&quot")
# *_emit methods for emitting nodes of the document
@@ -54,44 +55,44 @@ def text_emit(self, node):
return self.html_escape(node.content)
def separator_emit(self, node):
- return u'<hr>';
+ return u"<hr>"
def paragraph_emit(self, node):
- return u'<p>%s</p>\n' % self.emit_children(node)
+ return u"<p>%s</p>\n" % self.emit_children(node)
def bullet_list_emit(self, node):
- return u'<ul>\n%s</ul>\n' % self.emit_children(node)
+ return u"<ul>\n%s</ul>\n" % self.emit_children(node)
def number_list_emit(self, node):
- return u'<ol>\n%s</ol>\n' % self.emit_children(node)
+ return u"<ol>\n%s</ol>\n" % self.emit_children(node)
def list_item_emit(self, node):
- return u'<li>%s</li>\n' % self.emit_children(node)
+ return u"<li>%s</li>\n" % self.emit_children(node)
def table_emit(self, node):
- return u'<table>\n%s</table>\n' % self.emit_children(node)
+ return u"<table>\n%s</table>\n" % self.emit_children(node)
def table_row_emit(self, node):
- return u'<tr>%s</tr>\n' % self.emit_children(node)
+ return u"<tr>%s</tr>\n" % self.emit_children(node)
def table_cell_emit(self, node):
- return u'<td>%s</td>' % self.emit_children(node)
+ return u"<td>%s</td>" % self.emit_children(node)
def table_head_emit(self, node):
- return u'<th>%s</th>' % self.emit_children(node)
+ return u"<th>%s</th>" % self.emit_children(node)
def emphasis_emit(self, node):
- return u'<i>%s</i>' % self.emit_children(node)
+ return u"<i>%s</i>" % self.emit_children(node)
def strong_emit(self, node):
- return u'<b>%s</b>' % self.emit_children(node)
+ return u"<b>%s</b>" % self.emit_children(node)
def header_emit(self, node):
- return u'<h%d>%s</h%d>\n' % (
+ return u"<h%d>%s</h%d>\n" % (
node.level, self.html_escape(node.content), node.level)
def code_emit(self, node):
- return u'<tt>%s</tt>' % self.html_escape(node.content)
+ return u"<tt>%s</tt>" % self.html_escape(node.content)
def link_emit(self, node):
target = node.content
@@ -101,10 +102,10 @@ def link_emit(self, node):
inside = self.html_escape(target)
m = self.addr_re.match(target)
if m:
- if m.group('extern_addr'):
+ if m.group("extern_addr"):
return u'<a href="%s">%s</a>' % (
self.attr_escape(target), inside)
- elif m.group('inter_wiki'):
+ elif m.group("inter_wiki"):
raise NotImplementedError
return u'<a href="%s">%s</a>' % (
self.attr_escape(target), inside)
@@ -114,10 +115,10 @@ def image_emit(self, node):
text = self.get_text(node)
m = self.addr_re.match(target)
if m:
- if m.group('extern_addr'):
+ if m.group("extern_addr"):
return u'<img src="%s" alt="%s">' % (
self.attr_escape(target), self.attr_escape(text))
- elif m.group('inter_wiki'):
+ elif m.group("inter_wiki"):
raise NotImplementedError
return u'<img src="%s" alt="%s">' % (
self.attr_escape(target), self.attr_escape(text))
View
278 biblion/utils/mdx_codehilite.py
@@ -0,0 +1,278 @@
+#!/usr/bin/python
+import markdown
+
+"""
+File from https://code.achinghead.com/browser/py-markdown-ext/codehilite/trunk/mdx_codehilite.py?rev=22
+"""
+
+
+# --------------- CONSTANTS YOU MIGHT WANT TO MODIFY -----------------
+
+DEFAULT_HILITER = 'pygments' # one of 'enscript', 'dp', or 'pygments'
+try:
+ TAB_LENGTH = markdown.TAB_LENGTH
+except AttributeError:
+ TAB_LENGTH = 4
+
+# --------------- THE CODE -------------------------------------------
+# --------------- hiliter utility functions --------------------------
+def escape(txt) :
+ '''basic html escaping'''
+ txt = txt.replace('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+def number(txt):
+ '''use <ol> for line numbering'''
+ # Fix Whitespace
+ txt = txt.replace('\t', ' '*TAB_LENGTH)
+ txt = txt.replace(" "*4, "&nbsp; &nbsp; ")
+ txt = txt.replace(" "*3, "&nbsp; &nbsp;")
+ txt = txt.replace(" "*2, "&nbsp; ")
+
+ # Add line numbers
+ lines = txt.splitlines()
+ txt = '<div class="codehilite"<pre><ol>\n'
+ for line in lines:
+ txt += '\t<li>%s</li>\n'% line
+ txt += '</ol></pre></div>\n'
+ return txt
+
+# ---------------- The hiliters ---------------------------------------
+def enscript(src, lang=None, num=True):
+ '''
+Pass source code on to [enscript] (http://www.codento.com/people/mtr/genscript/)
+command line utility for hiliting.
+
+Usage:
+ >>> enscript(src [, lang [, num ]] )
+
+ @param src: Can be a string or any object with a .readline attribute.
+
+ @param lang: The language of code. Basic escaping only, if None.
+
+ @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default).
+
+ @returns : A string of html.
+ '''
+ if lang:
+ cmd = 'enscript --highlight=%s --color --language=html --tabsize=%d --output=-'% (lang, TAB_LENGTH)
+ from os import popen3
+ (i, out, err) = popen3(cmd)
+ i.write(src)
+ i.close()
+ # check for errors
+ e = err.read()
+ if e != 'output left in -\n' :
+ # error - just escape
+ txt = escape(src)
+ else :
+ import re
+ pattern = re.compile(r'<PRE>(?P<code>.*?)</PRE>', re.DOTALL)
+ txt = pattern.search(out.read()).group('code')
+ # fix enscripts output
+ txt = txt.replace('\n</FONT></I>', '</FONT></I>\n').strip()
+ html_map = {'<I>' : '<em>',
+ '</I>' : '</em>',
+ '<B>' : '<strong>',
+ '</B>' : '</strong>',
+ '<FONT COLOR="#' : '<span style="color:#',
+ '</FONT>' : '</span>'
+ }
+ for k, v in html_map.items() :
+ txt = txt.replace(k, v)
+ else:
+ txt = escape(src)
+ if num :
+ txt = number(txt)
+ else :
+ txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt
+ return txt
+
+
+def dp(src, lang=None, num=True):
+ '''
+Pass source code to a textarea for the [dp.SyntaxHighlighter] (http://www.dreamprojections.com/syntaxhighlighter/Default.aspx)
+
+Usage:
+ >>> dp(src [, lang [, num ]] )
+
+ @param src: A string.
+
+ @param lang: The language of code. Undefined if None.
+
+ @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default).
+
+ @returns : A string of html.
+ '''
+ gutter = ''
+ if not num:
+ gutter = ':nogutter'
+ if not lang:
+ lang = ''
+
+ return '<div class="codehilite"><textarea name="code" class="%s%s" cols="60" rows="10">\n%s\n</textarea></div>\n'% (lang, gutter, src)
+
+def pygment(src, lang = None, num = True):
+ '''
+Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with
+optional line numbers. The output should then be styled with css to your liking.
+No styles are applied by default - only styling hooks (i.e.: <span class="k">).
+
+Usage:
+ >>> pygment(src [, lang [, num ]] )
+
+ @param src: Can be a string or any object with a .readline attribute.
+
+ @param lang: The language of code. Pygments will try to guess language if None.
+
+ @param num: (Boolen) Turns line numbering 'on' or 'off' (on by default).
+
+ @returns : A string of html.
+ '''
+ try:
+ from pygments import highlight
+ from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer
+ from pygments.formatters import HtmlFormatter
+ except ImportError:
+ # just escape and pass through
+ txt = escape(src)
+ if num:
+ txt = number(txt)
+ else :
+ txt = '<div class="codehilite"><pre>%s</pre></div>\n'% txt
+ return txt
+ else:
+ try:
+ lexer = get_lexer_by_name(lang)
+ except ValueError:
+ try:
+ lexer = guess_lexer(src)
+ except ValueError:
+ lexer = TextLexer()
+ formatter = HtmlFormatter(linenos=num, cssclass="codehilite")
+ return highlight(src, lexer, formatter)
+
+
+# ------------------ The Main CodeHilite Class ----------------------
+class CodeHilite:
+ '''
+A wrapper class providing a single API for various hilighting engines. Takes source code, determines which language it containes (if not provided), and passes it into the hiliter specified.
+
+Basic Usage:
+ >>> code = CodeHilite(src = text)
+ >>> html = code.hilite()
+
+ @param src: Can be a string or any object with a .readline attribute.
+
+ @param lang: A string. Accepted values determined by hiliter used. Overrides _getLang()
+
+ @param linenos: (Boolen) Turns line numbering 'on' or 'off' (off by default).
+
+ @param hiliter: A string. One of 'enscript', 'dp', or 'pygments'.
+
+Low Level Usage:
+ >>> code = CodeHilite()
+ >>> code.src = text # Can be a string or any object with a .readline attribute.
+ >>> code.lang = 'python' # Setting this will override _getLang()
+ >>> code.linenos = True # True or False; Turns line numbering on or off.
+ >>> code.hiliter = MyCustomHiliter # Where MyCustomHiliter is callable, takes three arguments (src, lang, linenos) and returns a string.
+ >>> html = code.hilite()
+ '''
+ def __init__(self, src=None, lang=None, linenos = False, hiliter=DEFAULT_HILITER):
+ self.src = src
+ self.lang = lang
+ self.linenos = linenos
+ # map of highlighters
+ hl_map = { 'enscript' : enscript, 'dp' : dp, 'pygments' : pygment }
+ try :
+ self.hiliter = hl_map[hiliter]
+ except KeyError:
+ raise "Please provide a valid hiliter as a string. One of 'enscript', 'dp', or 'pygments'"
+
+
+ def _getLang(self):
+ '''
+Determines language of a code block from shebang lines and whether said line should be removed or left in place. If the sheband line contains a path (even a single /) then it is assumed to be a real shebang lines and left alone. However, if no path is given (e.i.: #!python or :::python) then it is assumed to be a mock shebang for language identifitation of a code fragment and removed from the code block prior to processing for code highlighting. When a mock shebang (e.i: #!python) is found, line numbering is turned on. When colons are found in place of a shebang (e.i.: :::python), line numbering is left in the current state - off by default.
+ '''
+ import re
+
+ #split text into lines
+ lines = self.src.split("\n")
+ #pull first line to examine
+ fl = lines.pop(0)
+
+ c = re.compile(r'''
+ (?:(?:::+)|(?P<shebang>[#]!)) #shebang or 2 or more colons
+ (?P<path>(?:/\w+)*[/ ])? # zero or 1 path ending in either a / or a single space
+ (?P<lang>\w*) # the language (a single / or space before lang is a path)
+ ''', re.VERBOSE)
+ # search first line for shebang
+ m = c.search(fl)
+
+ if m:
+ # we have a match
+ try:
+ self.lang = m.group('lang').lower()
+ except IndexError:
+ self.lang = None
+ if m.group('path') and m.group('path').strip(): #We need to avoid matching empty paths in ":::\b*bash"
+ # path exists - restore first line
+ print m.group('path'), "path found for", m.group('lang')
+ lines.insert(0, fl)
+ if m.group('shebang'):
+ # shebang exists - use line numbers
+ self.linenos = True
+ else:
+ # No match
+ lines.insert(0, fl)
+
+ self.src = "\n".join(lines).rstrip("\n")
+
+ def hilite(self):
+ '''The wrapper function which brings it all togeather'''
+ self.src = self.src.strip('\n')
+
+ if not self.lang : self._getLang()
+
+ return self.hiliter(self.src, self.lang, self.linenos)
+
+
+# ------------------ The Markdown Extention -------------------------------
+class CodeHiliteExtention (markdown.Extension) :
+ def __init__(self, configs):
+ # define default configs
+ self.config = {'hiliter' : [DEFAULT_HILITER, "one of 'enscript', 'dp', or 'pygments'"],
+ 'force_linenos' : [False, "Force line numbers - Default: False"] }
+
+ # Override defaults with user settings
+ if configs:
+ for key, value in configs :
+ # self.config[key][0] = value
+ self.setConfig(key, value)
+
+ def extendMarkdown(self, md) :
+
+ def _hiliteCodeBlock(parent_elem, lines, inList):
+ """Overrides function of same name in standard Markdown class and
+ sends code blocks to a code highlighting proccessor. The result
+ is then stored in the HtmlStash, a placeholder is inserted into
+ the dom and the remainder of the text file is processed recursively.
+
+ @param parent_elem: DOM element to which the content will be added
+ @param lines: a list of lines
+ @param inList: a level
+ @returns: None"""
+ detabbed, theRest = md.blockGuru.detectTabbed(lines)
+ text = "\n".join(detabbed).rstrip()+"\n"
+ code = CodeHilite(text, hiliter=self.config['hiliter'][0], linenos=self.config['force_linenos'][0])
+ placeholder = md.htmlStash.store(code.hilite())
+ parent_elem.appendChild(md.doc.createTextNode(placeholder))
+ md._processSection(parent_elem, theRest, inList)
+
+ md._processCodeBlock = _hiliteCodeBlock
+
+def makeExtension(configs=None) :
+ return CodeHiliteExtention(configs=configs)
View
13 biblion/utils/slugify.py
@@ -0,0 +1,13 @@
+from django.template.defaultfilters import slugify
+
+
+def slugify_unique(value, model, slugfield="slug"):
+ suffix = 0
+ potential = base = slugify(value)
+ while True:
+ if suffix:
+ potential = "-".join([base, str(suffix)])
+ if not model.objects.filter(**{slugfield: potential}).count():
+ print model.objects.filter(**{slugfield: potential})
+ return potential
+ suffix += 1
View
13 biblion/utils/twitter.py
@@ -0,0 +1,13 @@
+from django.conf import settings
+
+try:
+ import twitter
+except ImportError:
+ twitter = None
+
+
+def can_tweet():
+ creds_available = (
+ hasattr(settings, "TWITTER_USERNAME") and hasattr(settings, "TWITTER_PASSWORD")
+ )
+ return twitter and creds_available
View
6 requirements.txt
@@ -1,6 +1,2 @@
-# required
-creole==1.2
-Pygments==1.2.2
-
# optional
-python-twitter==0.8.1
+python-twitter==0.8.1
View
25 setup.py
@@ -1,4 +1,4 @@
-from distutils.core import setup
+from setuptools import setup, find_packages
# see requirements.txt for dependencies
@@ -13,14 +13,14 @@
long_description = open("README.rst").read(),
license = "BSD",
url = "http://github.com/eldarion/biblion",
- packages = [
- "biblion",
+ packages=find_packages(),
+ install_requires=[
+ "creole==1.2",
+ "docutils==0.8.1",
+ "Markdown==2.0.3",
+ "Pygments==1.4",
+ "textile==2.1.5",
],
- package_data = {
- "biblion": [
- "templates/biblion/*.xml",
- ]
- },
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
@@ -29,5 +29,12 @@
"Operating System :: OS Independent",
"Programming Language :: Python",
"Framework :: Django",
- ]
+ ],
+ # Make setuptools include all data files under version control,
+ # svn and CVS by default
+ include_package_data=True,
+ # Tells setuptools to download setuptools_git before running setup.py so
+ # it can find the data files under Git version control.
+ setup_requires=["setuptools_git"],
+ zip_safe=False,
)
Something went wrong with that request. Please try again.