Added hgignore, refactored code to regular SQLAlchemy and newer werkzeug features.

This commit is contained in:
mitsuhiko 2008-06-23 12:08:50 +02:00
parent 0d8803bb88
commit 3fec765590
16 changed files with 129 additions and 224 deletions

1
.hgignore Normal file
View File

@ -0,0 +1 @@
\.py[co]$

View File

@ -12,10 +12,13 @@
import os
import sqlalchemy
from datetime import datetime, timedelta
from werkzeug import SharedDataMiddleware, ClosingIterator
from werkzeug.exceptions import HTTPException, NotFound
from lodgeit.utils import _local_manager, ctx, jinja_environment, \
Request, generate_user_hash, NotFound, RequestRedirect, redirect
from lodgeit.database import metadata, db, Paste
Request, generate_user_hash
from lodgeit.database import metadata, session, Paste
from lodgeit.urls import urlmap
from lodgeit.controllers import get_controller
from lodgeit.lib.antispam import AntiSpam
@ -23,9 +26,7 @@ from lodgeit.lib.antispam import AntiSpam
class LodgeIt(object):
"""
The WSGI Application
"""
"""The WSGI Application"""
def __init__(self, dburi):
#: name of the error handler
@ -41,9 +42,7 @@ class LodgeIt(object):
ctx.application = self
def __call__(self, environ, start_response):
"""
Minimal WSGI application for request dispatching.
"""
"""Minimal WSGI application for request dispatching."""
#: bind the application to the new context local
self.bind_to_context()
request = Request(environ)
@ -56,8 +55,8 @@ class LodgeIt(object):
except NotFound:
handler = get_controller(self.not_found[0])
resp = handler(**self.not_found[1])
except RequestRedirect, err:
resp = redirect(err.new_url)
except HTTPException, e:
resp = e.get_response(environ)
else:
if request.first_visit:
resp.set_cookie('user_hash', request.user_hash,
@ -65,13 +64,11 @@ class LodgeIt(object):
)
return ClosingIterator(resp(environ, start_response),
[_local_manager.cleanup, db.session.remove])
[_local_manager.cleanup, session.remove])
def make_app(dburi, debug=False, shell=False):
"""
Apply the used middlewares and create the application.
"""
"""Apply the used middlewares and create the application."""
static_path = os.path.join(os.path.dirname(__file__), 'static')
app = LodgeIt(dburi)
if debug:

View File

@ -5,13 +5,15 @@
The paste controller
:copyright: 2007 by Armin Ronacher, Christopher Grebs.
:copyright: 2007-2008 by Armin Ronacher, Christopher Grebs.
:license: BSD
"""
from lodgeit.utils import redirect, PageNotFound, Response, \
ctx, render_template
from werkzeug import redirect, Response
from werkzeug.exceptions import NotFound
from lodgeit.utils import ctx, render_template
from lodgeit.controllers import BaseController
from lodgeit.database import db, Paste
from lodgeit.database import session, Paste
from lodgeit.lib.highlighting import LANGUAGES, STYLES, get_style
from lodgeit.lib.pagination import generate_pagination
@ -20,17 +22,13 @@ MAX_LINE_LENGTH = 300
class PasteController(BaseController):
"""
Provides all the handler callback for paste related stuff.
"""
"""Provides all the handler callback for paste related stuff."""
def new_paste(self):
"""
The 'create a new paste' view.
"""
"""The 'create a new paste' view."""
code = error = ''
language = 'text'
pastes = db.session.query(Paste)
pastes = session.query(Paste)
if ctx.request.method == 'POST':
code = ctx.request.form.get('code')
@ -46,8 +44,8 @@ class PasteController(BaseController):
error = 'contains spam'
if code and language and not error:
paste = Paste(code, language, parent, ctx.request.user_hash)
db.session.save(paste)
db.session.flush()
session.save(paste)
session.flush()
return redirect(paste.url)
else:
@ -66,21 +64,16 @@ class PasteController(BaseController):
)
def show_paste(self, paste_id, raw=False):
"""
Show an existing paste.
"""
"""Show an existing paste."""
linenos = ctx.request.args.get('linenos') != 'no'
pastes = db.session.query(Paste)
pastes = session.query(Paste)
paste = pastes.filter(Paste.c.paste_id == paste_id).first()
if paste is None:
raise PageNotFound()
raise NotFound()
if raw:
return Response(paste.code, mimetype='text/plain; charset=utf-8')
style, css = get_style(ctx.request)
paste.rehighlight(linenos)
return render_template('show_paste.html',
paste=paste,
style=style,
@ -90,38 +83,33 @@ class PasteController(BaseController):
)
def raw_paste(self, paste_id):
"""
Show an existing paste in raw mode.
"""
"""Show an existing paste in raw mode."""
return self.show_paste(paste_id, raw=True)
def show_tree(self, paste_id):
"""
Display the tree of some related pastes.
"""
"""Display the tree of some related pastes."""
paste = Paste.resolve_root(paste_id)
if paste is None:
raise PageNotFound()
raise NotFound()
return render_template('paste_tree.html',
paste=paste,
current=paste_id
)
def show_all(self, page=1):
"""
Paginated list of pages.
"""
raise PageNotFound() #XXX: activate again?
"""Paginated list of pages."""
raise NotFound()
def link(page):
if page == 1:
return '/all/'
return '/all/%d' % page
pastes = db.session.query(Paste).order_by(
pastes = session.query(Paste).order_by(
Paste.c.pub_date.desc()
).limit(10).offset(10*(page-1))
if not pastes and page != 1:
raise PageNotFound()
raise NotFound()
for paste in pastes:
paste.rehighlight()
@ -142,11 +130,11 @@ class PasteController(BaseController):
old_id = ctx.request.form.get('old', '-1').lstrip('#')
new_id = ctx.request.form.get('new', '-1').lstrip('#')
return redirect('/compare/%s/%s' % (old_id, new_id))
pastes = db.session.query(Paste)
pastes = session.query(Paste)
old = pastes.filter(Paste.c.paste_id == old_id).first()
new = pastes.filter(Paste.c.paste_id == new_id).first()
if old is None or new is None:
raise PageNotFound()
raise NotFound()
return render_template('compare_paste.html',
old=old,
new=new,
@ -157,11 +145,11 @@ class PasteController(BaseController):
"""
Render an udiff for the two pastes.
"""
pastes = db.session.query(Paste)
pastes = session.query(Paste)
old = pastes.filter(Paste.c.paste_id == old_id).first()
new = pastes.filter(Paste.c.paste_id == new_id).first()
if old is None or new is None:
raise PageNotFound()
raise NotFound()
return Response(old.compare_to(new), mimetype='text/plain')
def set_colorscheme(self):

View File

@ -5,10 +5,12 @@
Static stuff.
:copyright: 2007 by Armin Ronacher.
:copyright: 2007-2008 by Armin Ronacher.
:license: BSD
"""
from lodgeit.utils import ctx, PageNotFound, render_template
from werkzeug.exceptions import NotFound
from lodgeit.utils import ctx, render_template
from lodgeit.controllers import BaseController
from lodgeit.lib.xmlrpc import xmlrpc
@ -37,7 +39,7 @@ class StaticController(BaseController):
elif topic in known_help_pages:
tmpl_name = 'help/%s.html' % topic
else:
raise PageNotFound()
raise NotFound()
return render_template(
tmpl_name,
help_topics=HELP_PAGES,

View File

@ -10,10 +10,10 @@
"""
from lodgeit.utils import ctx, render_template
from lodgeit.controllers import BaseController
from lodgeit.database import db, Paste
from lodgeit.database import session, Paste
from lodgeit.lib.xmlrpc import xmlrpc, exported
from lodgeit.lib.highlighting import STYLES, LANGUAGES, get_style, \
get_language_for
get_language_for
class XmlRpcController(BaseController):
@ -27,8 +27,7 @@ class XmlRpcController(BaseController):
@exported('pastes.newPaste')
def pastes_new_paste(language, code, parent_id=None,
filename='', mimetype=''):
"""
Create a new paste. Return the new ID.
"""Create a new paste. Return the new ID.
`language` can be None, in which case the language will be
guessed from `filename` and/or `mimetype`.
@ -36,21 +35,20 @@ def pastes_new_paste(language, code, parent_id=None,
if not language:
language = get_language_for(filename or '', mimetype or '')
paste = Paste(code, language, parent_id)
db.session.save(paste)
db.session.flush()
session.save(paste)
session.flush()
return paste.paste_id
@exported('pastes.getPaste')
def pastes_get_paste(paste_id):
"""
Get all known information about a paste by a given paste id.
"""Get all known information about a paste by a given paste id.
Return a dictionary with these keys:
`paste_id`, `code`, `parsed_code`, `pub_date`, `language`,
`parent_id`, `url`.
"""
paste = db.session.query(Paste).filter(Paste.c.paste_id ==
paste = session.query(Paste).filter(Paste.c.paste_id ==
paste_id).first()
if paste is None:
return False
@ -59,10 +57,8 @@ def pastes_get_paste(paste_id):
@exported('pastes.getDiff')
def pastes_get_diff(old_id, new_id):
"""
Compare the two pastes and return an unified diff.
"""
pastes = db.session.query(Paste)
"""Compare the two pastes and return an unified diff."""
pastes = session.query(Paste)
old = pastes.filter(Paste.c.paste_id == old_id).first()
new = pastes.filter(Paste.c.paste_id == new_id).first()
if old is None or new is None:
@ -72,21 +68,19 @@ def pastes_get_diff(old_id, new_id):
@exported('pastes.getRecent')
def pastes_get_recent(amount=5):
"""
Return information dict (see `getPaste`) about the last `amount` pastes.
"""Return information dict (see `getPaste`) about the last
`amount` pastes.
"""
amount = min(amount, 20)
return [x.to_xmlrpc_dict() for x in
db.session.query(Paste).order_by(
session.query(Paste).order_by(
Paste.pub_date.desc()
).limit(amount)]
@exported('pastes.getLast')
def pastes_get_last():
"""
Get information dict (see `getPaste`) for the most recent paste.
"""
"""Get information dict (see `getPaste`) for the most recent paste."""
rv = pastes_get_recent(1)
if rv:
return rv[0]
@ -95,25 +89,19 @@ def pastes_get_last():
@exported('pastes.getLanguages')
def pastes_get_languages():
"""
Get a list of supported languages.
"""
"""Get a list of supported languages."""
return LANGUAGES.items()
@exported('styles.getStyles')
def styles_get_styles():
"""
Get a list of supported styles.
"""
"""Get a list of supported styles."""
return STYLES.items()
@exported('styles.getStylesheet')
def styles_get_stylesheet(name):
"""
Return the stylesheet for a given style.
"""
"""Return the stylesheet for a given style."""
return get_style(name)

View File

@ -10,53 +10,35 @@
"""
import time
import difflib
from types import ModuleType
import sqlalchemy
from sqlalchemy import orm
from sqlalchemy.orm.scoping import ScopedSession
from cgi import escape
from datetime import datetime
from sqlalchemy import MetaData, Integer, Text, DateTime, ForeignKey, \
String, Boolean, Table, Column, select, and_, func
from sqlalchemy.orm import create_session, mapper, backref, relation
from sqlalchemy.orm.scoping import ScopedSession
from werkzeug import cached_property
from lodgeit.utils import _local_manager, ctx
from lodgeit.lib.highlighting import highlight, LANGUAGES
def session_factory():
return orm.create_session(bind=ctx.application.engine)
session = ScopedSession(lambda: create_session(bind=ctx.application.engine),
scopefunc=_local_manager.get_ident)
metadata = MetaData()
session = ScopedSession(session_factory, scopefunc=_local_manager.get_ident)
#: create a fake module for easy access to database session methods
db = ModuleType('db')
key = value = mod = None
for mod in sqlalchemy, orm:
for key, value in mod.__dict__.iteritems():
if key in mod.__all__:
setattr(db, key, value)
del key, mod, value
db.__doc__ = __doc__
for name in 'delete', 'save', 'flush', 'execute', 'begin', 'query', \
'commit', 'rollback', 'clear', 'refresh', 'expire':
setattr(db, name, getattr(session, name))
db.session = session
metadata = db.MetaData()
pastes = db.Table('pastes', metadata,
db.Column('paste_id', db.Integer, primary_key=True),
db.Column('code', db.String),
db.Column('parsed_code', db.String),
db.Column('parent_id', db.Integer, db.ForeignKey('pastes.paste_id'),
nullable=True),
db.Column('pub_date', db.DateTime),
db.Column('language', db.String(30)),
db.Column('user_hash', db.String(40), nullable=True),
db.Column('handled', db.Boolean, nullable=False)
pastes = Table('pastes', metadata,
Column('paste_id', Integer, primary_key=True),
Column('code', Text),
Column('parent_id', Integer, ForeignKey('pastes.paste_id'),
nullable=True),
Column('pub_date', DateTime),
Column('language', String(30)),
Column('user_hash', String(40), nullable=True),
Column('handled', Boolean, nullable=False)
)
class Paste(object):
def __init__(self, code, language, parent=None, user_hash=None):
@ -91,8 +73,9 @@ class Paste(object):
return rv and rv[0] or None
return udiff
def rehighlight(self, linenos=True):
self.parsed_code = highlight(self.code, self.language, linenos)
@cached_property
def parsed_code(self):
return highlight(self.code, self.language)
def to_xmlrpc_dict(self):
from lodgeit.lib.xmlrpc import strip_control_chars
@ -120,33 +103,32 @@ class Paste(object):
@staticmethod
def fetch_replies():
"""
Get the new replies for the ower of a request and flag them
"""Get the new replies for the ower of a request and flag them
as handled.
"""
s = db.select([pastes.c.paste_id],
s = select([pastes.c.paste_id],
Paste.user_hash == ctx.request.user_hash
)
paste_list = db.session.query(Paste).filter(db.and_(
paste_list = session.query(Paste).filter(and_(
Paste.parent_id.in_(s),
Paste.handled == False,
Paste.user_hash != ctx.request.user_hash,
)).order_by(pastes.c.paste_id.desc()).all()
to_mark = [p.paste_id for p in paste_list]
db.execute(pastes.update(pastes.c.paste_id.in_(to_mark),
values={'handled': True}))
session.execute(pastes.update(pastes.c.paste_id.in_(to_mark),
values={'handled': True}))
return paste_list
@staticmethod
def count():
s = db.select([db.func.count(pastes.c.paste_id)])
return db.execute(s).fetchone()[0]
s = select([func.count(pastes.c.paste_id)])
return session.execute(s).fetchone()[0]
@staticmethod
def resolve_root(paste_id):
pastes = db.query(Paste)
pastes = session.query(Paste)
while True:
paste = pastes.filter(Paste.c.paste_id == paste_id).first()
if paste is None:
@ -156,10 +138,10 @@ class Paste(object):
paste_id = paste.parent_id
db.mapper(Paste, pastes, properties={
'children': db.relation(Paste,
mapper(Paste, pastes, properties={
'children': relation(Paste,
primaryjoin=pastes.c.parent_id==pastes.c.paste_id,
cascade='all',
backref=db.backref('parent', remote_side=[pastes.c.paste_id])
backref=backref('parent', remote_side=[pastes.c.paste_id])
)
})

View File

@ -28,8 +28,7 @@ def percentize(matched, length):
class AntiSpam(object):
"""
Class for fighting against that damn spammers. It's working not with
"""Class for fighting against that damn spammers. It's working not with
a flat file with some bad-content but with some other hopefully more
effective methods.
"""

View File

@ -13,24 +13,18 @@ from cgi import escape
def prepare_udiff(udiff):
"""
Prepare an udiff for a template
"""
renderer = DiffRenderer(udiff)
return renderer.prepare()
"""Prepare an udiff for a template"""
return DiffRenderer(udiff).prepare()
class DiffRenderer(object):
"""
Give it a unified diff and it renders you a beautiful
"""Give it a unified diff and it renders you a beautiful
html diff :-)
"""
_chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
def __init__(self, udiff):
"""
:param udiff: a text in udiff format
"""
""":param udiff: a text in udiff format"""
self.lines = [escape(line) for line in udiff.splitlines()]
def _extract_rev(self, line1, line2):
@ -44,9 +38,7 @@ class DiffRenderer(object):
return None, None, None
def _highlight_line(self, line, next):
"""
Highlight inline changes in both lines.
"""
"""Highlight inline changes in both lines."""
start = 0
limit = min(len(line['line']), len(next['line']))
while start < limit and line['line'][start] == next['line'][start]:
@ -74,9 +66,7 @@ class DiffRenderer(object):
do(next)
def _parse_udiff(self):
"""
Parse the diff an return data for the template.
"""
"""Parse the diff an return data for the template."""
lineiter = iter(self.lines)
files = []
try:

View File

@ -71,19 +71,15 @@ STYLES = dict((x, x.title()) for x in get_all_styles())
DEFAULT_STYLE = 'friendly'
def highlight(code, language, linenos):
"""
Highlight a given code to HTML
"""
def highlight(code, language):
"""Highlight a given code to HTML"""
lexer = get_lexer_by_name(language)
formatter = HtmlFormatter(linenos=linenos, cssclass='syntax', style='pastie')
formatter = HtmlFormatter(linenos=True, cssclass='syntax', style='pastie')
return pygments.highlight(code, lexer, formatter)
def get_style(request):
"""
Style for a given request
"""
"""Style for a given request"""
if isinstance(request, basestring):
style_name = request
else:
@ -100,9 +96,7 @@ def get_style(request):
def get_language_for(filename, mimetype):
"""
Get language for filename and mimetype
"""
"""Get language for filename and mimetype"""
# XXX: this instantiates a lexer just to get at its aliases
try:
lexer = get_lexer_for_mimetype(mimetype)

View File

@ -17,8 +17,7 @@ def generate_pagination(page, per_page, total, link_builder=None,
commata=',\n', ellipsis=' ...\n', threshold=3,
prev_link=True, next_link=True,
gray_prev_link=True, gray_next_link=True):
"""
Generates a pagination.
"""Generates a pagination.
:param page: current page number
:param per_page: items per page

View File

@ -12,7 +12,10 @@ import sys
import re
import inspect
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
from lodgeit.utils import ctx, Response
from werkzeug import Response
from lodgeit.utils import ctx
_strip_re = re.compile(r'[\x00-\x08\x0B-\x1F]')

View File

@ -93,12 +93,7 @@ var LodgeIt = {
* fade the line numbers in and out
*/
toggleLineNumbers : function() {
$('#paste td.linenos').each(function() {
var state = $(this).is(':hidden') ? 'show' : 'hide';
$(this).animate({
opacity: state
}, 200);
});
$('#paste').toggleClass('nolinenos');
},
/**

View File

@ -102,6 +102,10 @@ div.text {
border-spacing: 0;
}
#paste td.code {
padding-left: 10px;
}
#paste td.linenos {
background-color: #333;
padding-right: 5px;
@ -110,8 +114,8 @@ div.text {
color: #eee;
}
#paste .code, #paste.code {
padding-left: 10px;
#paste.nolinenos td.linenos {
display: none;
}
#paste pre {

View File

@ -15,25 +15,15 @@ try:
except:
from sha import new as sha1
from random import random
from types import ModuleType
from werkzeug import Local, LocalManager, LocalProxy, BaseRequest, \
BaseResponse
from werkzeug.routing import NotFound, RequestRedirect
from werkzeug import Local, LocalManager, LocalProxy, \
Request as RequestBase, Response
from jinja2 import Environment, FileSystemLoader
#: context locals
_local = Local()
_local_manager = LocalManager([_local])
#: fake module type for easy access to context locals
ctx = ModuleType('ctx')
ctx.__doc__ = 'Module that holds all context locals'
#: some variables for direct access to the context locals
ctx.application = LocalProxy(_local, 'application')
ctx.request = LocalProxy(_local, 'request')
ctx = Local()
_local_manager = LocalManager(ctx)
#: jinja environment for all the templates
jinja_environment = Environment(loader=FileSystemLoader(
@ -41,21 +31,19 @@ jinja_environment = Environment(loader=FileSystemLoader(
def datetimeformat(obj):
"""
Helper filter for the template
"""
"""Helper filter for the template"""
return obj.strftime('%Y-%m-%d %H:%M')
jinja_environment.filters['datetimeformat'] = datetimeformat
def generate_user_hash():
"""Generates an more or less unique SHA1 hash."""
return sha1('%s|%s' % (random(), time.time())).hexdigest()
class Request(BaseRequest):
"""
Subclass of the `BaseRequest` object. automatically creates a new
class Request(RequestBase):
"""Subclass of the `Request` object. automatically creates a new
`user_hash` and sets `first_visit` to `True` if it's a new user.
It also stores the engine and dbsession on it.
"""
@ -79,23 +67,8 @@ class Request(BaseRequest):
ctx.request = self
class Response(BaseResponse):
"""
Subclass the response object for later extension.
"""
charset = 'utf-8'
class PageNotFound(NotFound):
"""
Internal exception used to tell the application to show the
error page.
"""
def render_template(template_name, **tcontext):
"""
Render a template to a response. This automatically fetches
"""Render a template to a response. This automatically fetches
the list of new replies for the layout template. It also
adds the current request to the context. This is used for the
welcome message.
@ -106,13 +79,3 @@ def render_template(template_name, **tcontext):
tcontext['request'] = ctx.request
t = jinja_environment.get_template(template_name)
return Response(t.render(tcontext), mimetype='text/html; charset=utf-8')
def redirect(url, code=302):
"""
Redirect to somewhere. Returns a nice response object.
"""
return Response('Page Moved to %s' % url,
headers=[('Location', url),
('Content-Type', 'text/plain')],
status=code)

View File

@ -43,7 +43,7 @@
</ul>
</div>
</div>
<div id="paste"{% if not linenos %} class="code" {% endif %}>
<div id="paste" class="code{% if not linenos %} nolinenos{% endif %}">
{{ paste.parsed_code }}
</div>
{% endblock %}

View File

@ -1,6 +1,6 @@
from lodgeit.application import make_app
from lodgeit.utils import ctx
from lodgeit.database import db
from lodgeit.database import session
from werkzeug import script
from werkzeug.serving import run_simple
from werkzeug.utils import create_environ, run_wsgi_app
@ -19,12 +19,12 @@ action_shell = script.make_shell(
lambda: {
'app': make_app(dburi, False, True),
'ctx': ctx,
'db': db,
'session': session,
'run_app': run_app
},
('\nWelcome to the interactive shell environment of LodgeIt!\n'
'\n'
'You can use the following predefined objects: app, ctx, db.\n'
'You can use the following predefined objects: app, ctx, session.\n'
'To run the application (creates a request) use *run_app*.')
)