[svn] checked in incomplete lodgeit in order to install it as private beta on the server
This commit is contained in:
parent
a0ec7ac5ea
commit
b36b5e4106
10
lodgeit/__init__.py
Normal file
10
lodgeit/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit
|
||||
~~~~~~~
|
||||
|
||||
The lodgeit pastebin.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
146
lodgeit/application.py
Normal file
146
lodgeit/application.py
Normal file
@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.application
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
the WSGI application
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import os
|
||||
import sqlalchemy
|
||||
from datetime import datetime, timedelta
|
||||
from wsgitk.wrappers import BaseRequest, BaseResponse
|
||||
from wsgitk.static import StaticExports
|
||||
from jinja import Environment, PackageLoader
|
||||
|
||||
from lodgeit.urls import urlmap
|
||||
from lodgeit.controllers import get_controller
|
||||
from lodgeit.database import metadata, generate_user_hash, Paste
|
||||
|
||||
|
||||
#: jinja environment for all the templates
|
||||
jinja_environment = Environment(loader=PackageLoader('lodgeit', 'views',
|
||||
use_memcache=True,
|
||||
cache_folder='/tmp',
|
||||
auto_reload=True
|
||||
))
|
||||
|
||||
|
||||
def datetimeformat():
|
||||
"""
|
||||
Helper filter for the template
|
||||
"""
|
||||
def wrapped(env, ctx, value):
|
||||
return value.strftime('%Y-%m-%d %H:%M')
|
||||
return wrapped
|
||||
|
||||
jinja_environment.filters['datetimeformat'] = datetimeformat
|
||||
|
||||
|
||||
def render_template(req, template_name, **context):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if req.method == 'GET':
|
||||
context['new_replies'] = Paste.fetch_replies(req)
|
||||
context['request'] = req
|
||||
t = jinja_environment.get_template(template_name)
|
||||
return Response(t.render(context), 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=302)
|
||||
|
||||
|
||||
class Request(BaseRequest):
|
||||
"""
|
||||
Subclass of the `BaseRequest` 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.
|
||||
"""
|
||||
|
||||
def __init__(self, environ, engine):
|
||||
self.engine = engine
|
||||
self.dbsession = sqlalchemy.create_session(engine)
|
||||
super(Request, self).__init__(environ)
|
||||
|
||||
# check the user hash. an empty cookie is considered
|
||||
# begin a new session.
|
||||
self.user_hash = ''
|
||||
self.first_visit = False
|
||||
if 'user_hash' in self.cookies:
|
||||
self.user_hash = self.cookies['user_hash'].value
|
||||
if not self.user_hash:
|
||||
self.user_hash = generate_user_hash()
|
||||
self.first_visit = True
|
||||
|
||||
|
||||
class Response(BaseResponse):
|
||||
"""
|
||||
Subclass the response object for later extension.
|
||||
"""
|
||||
|
||||
|
||||
class PageNotFound(Exception):
|
||||
"""
|
||||
Internal exception used to tell the application to show the
|
||||
error page.
|
||||
"""
|
||||
|
||||
|
||||
class LodgeIt(object):
|
||||
"""
|
||||
The WSGI Application
|
||||
"""
|
||||
|
||||
def __init__(self, dburi):
|
||||
#: name of the error handler
|
||||
self.not_found = ('static/not_found', {})
|
||||
self.engine = sqlalchemy.create_engine(dburi)
|
||||
#: make sure all tables exist.
|
||||
metadata.create_all(self.engine)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""
|
||||
Minimal WSGI application for request dispatching.
|
||||
"""
|
||||
req = Request(environ, self.engine)
|
||||
rv = urlmap.test(environ.get('PATH_INFO', ''))
|
||||
try:
|
||||
if rv is None:
|
||||
raise PageNotFound()
|
||||
handler = get_controller(rv[0], req)
|
||||
response = handler(**rv[1])
|
||||
except PageNotFound:
|
||||
handler = get_controller(self.not_found[0], req)
|
||||
response = handler(**self.not_found[1])
|
||||
# on first visit we send out the cookie
|
||||
if req.first_visit:
|
||||
response.set_cookie('user_hash', req.user_hash,
|
||||
expires=datetime.utcnow() + timedelta(days=31)
|
||||
)
|
||||
# call the response as WSGI app
|
||||
return response(environ, start_response)
|
||||
|
||||
|
||||
def make_app(dburi):
|
||||
"""
|
||||
Apply the used middlewares and create the application.
|
||||
"""
|
||||
static_path = os.path.join(os.path.dirname(__file__), 'static')
|
||||
app = LodgeIt(dburi)
|
||||
app = StaticExports(app, {
|
||||
'/static': static_path
|
||||
})
|
||||
return app
|
27
lodgeit/controllers/__init__.py
Normal file
27
lodgeit/controllers/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.controllers
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Module that helds the controllers
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
|
||||
class BaseController(object):
|
||||
"""
|
||||
Base controller. add some stuff to the dict on instanciation
|
||||
"""
|
||||
|
||||
def __init__(self, req):
|
||||
self.request = req
|
||||
self.engine = req.engine
|
||||
self.dbsession = req.dbsession
|
||||
|
||||
|
||||
def get_controller(name, req):
|
||||
cname, hname = name.split('/')
|
||||
module = __import__('lodgeit.controllers.' + cname, None, None, [''])
|
||||
controller = module.controller(req)
|
||||
return getattr(controller, hname)
|
134
lodgeit/controllers/pastes.py
Normal file
134
lodgeit/controllers/pastes.py
Normal file
@ -0,0 +1,134 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.controllers.pastes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The paste controller
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import sqlalchemy as meta
|
||||
|
||||
from lodgeit.application import render_template, redirect, PageNotFound
|
||||
from lodgeit.controllers import BaseController
|
||||
from lodgeit.database import Paste
|
||||
from lodgeit.lib.highlighting import LANGUAGES, STYLES, get_style
|
||||
from lodgeit.lib.pagination import generate_pagination
|
||||
|
||||
|
||||
class PasteController(BaseController):
|
||||
"""
|
||||
Provides all the handler callback for paste related stuff.
|
||||
"""
|
||||
|
||||
def new_paste(self):
|
||||
"""
|
||||
The 'create a new paste' view.
|
||||
"""
|
||||
pastes = self.dbsession.query(Paste)
|
||||
if self.request.method == 'POST':
|
||||
code = self.request.POST.get('code')
|
||||
language = self.request.POST.get('language')
|
||||
parent = self.request.POST.get('parent')
|
||||
if parent is not None:
|
||||
parent = pastes.selectfirst(Paste.c.paste_id == parent)
|
||||
if code and language:
|
||||
paste = Paste(code, language, parent, self.request.user_hash)
|
||||
self.dbsession.save(paste)
|
||||
self.dbsession.flush()
|
||||
return redirect(paste.url)
|
||||
|
||||
parent = self.request.GET.get('reply_to')
|
||||
if parent is not None:
|
||||
parent = pastes.selectfirst(Paste.c.paste_id == parent)
|
||||
|
||||
return render_template(self.request, 'new_paste.html',
|
||||
languages=LANGUAGES,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
def show_paste(self, paste_id):
|
||||
"""
|
||||
Show an existing paste.
|
||||
"""
|
||||
pastes = self.dbsession.query(Paste)
|
||||
paste = pastes.selectfirst(Paste.c.paste_id == paste_id)
|
||||
if paste is None:
|
||||
raise PageNotFound()
|
||||
style, css = get_style(self.request)
|
||||
return render_template(self.request, 'show_paste.html',
|
||||
paste=paste,
|
||||
style=style,
|
||||
css=css,
|
||||
styles=STYLES
|
||||
)
|
||||
|
||||
def show_tree(self, paste_id):
|
||||
"""
|
||||
Display the tree of some related pastes.
|
||||
"""
|
||||
paste = Paste.resolve_root(self.dbsession, paste_id)
|
||||
if paste is None:
|
||||
raise PageNotFound()
|
||||
return render_template(self.request, 'paste_tree.html',
|
||||
paste=paste,
|
||||
current=paste_id
|
||||
)
|
||||
|
||||
def show_all(self, page=1):
|
||||
"""
|
||||
Paginated list of pages.
|
||||
"""
|
||||
def link(page):
|
||||
if page == 1:
|
||||
return '/all/'
|
||||
return '/all/%d' % page
|
||||
|
||||
pastes = self.dbsession.query(Paste).select(
|
||||
order_by=[meta.desc(Paste.c.pub_date)],
|
||||
limit=10,
|
||||
offset=10 * (page - 1)
|
||||
)
|
||||
if not pastes and page != 1:
|
||||
raise PageNotFound()
|
||||
|
||||
return render_template(self.request, 'show_all.html',
|
||||
pastes=pastes,
|
||||
pagination=generate_pagination(page, 10,
|
||||
Paste.count(self.request.engine), link),
|
||||
css=get_style(self.request)[1]
|
||||
)
|
||||
|
||||
def compare_paste(self, new_id=None, old_id=None):
|
||||
"""
|
||||
Render a diff view for two pastes.
|
||||
"""
|
||||
# redirect for the compare form box
|
||||
if old_id is new_id is None:
|
||||
old_id = self.request.POST.get('old', '-1').lstrip('#')
|
||||
new_id = self.request.POST.get('new', '-1').lstrip('#')
|
||||
return redirect('/compare/%s/%s' % (old_id, new_id))
|
||||
pastes = self.dbsession.query(Paste)
|
||||
old = pastes.selectfirst(Paste.c.paste_id == old_id)
|
||||
new = pastes.selectfirst(Paste.c.paste_id == new_id)
|
||||
if old is None or new is None:
|
||||
raise PageNotFound()
|
||||
return render_template(self.request, 'compare_paste.html',
|
||||
old=old,
|
||||
new=new,
|
||||
diff=old.compare_to(new, template=True)
|
||||
)
|
||||
|
||||
def set_colorscheme(self):
|
||||
"""
|
||||
Minimal view that updates the style session cookie. Redirects
|
||||
back to the page the user is coming from.
|
||||
"""
|
||||
style_name = self.request.POST.get('style')
|
||||
resp = redirect(self.request.environ.get('HTTP_REFERER') or '/')
|
||||
if style_name in STYLES:
|
||||
resp.set_cookie('style', style_name)
|
||||
return resp
|
||||
|
||||
controller = PasteController
|
23
lodgeit/controllers/static.py
Normal file
23
lodgeit/controllers/static.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.controllers.static
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Static stuff.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
from lodgeit.application import render_template
|
||||
from lodgeit.controllers import BaseController
|
||||
|
||||
|
||||
class StaticController(BaseController):
|
||||
|
||||
def not_found(self):
|
||||
return render_template(self.request, 'not_found.html')
|
||||
|
||||
def about(self):
|
||||
return render_template(self.request, 'about.html')
|
||||
|
||||
controller = StaticController
|
91
lodgeit/controllers/xmlrpc.py
Normal file
91
lodgeit/controllers/xmlrpc.py
Normal file
@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.controllers.xmlrpc
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The XMLRPC controller
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import sqlalchemy as meta
|
||||
|
||||
from lodgeit.application import render_template
|
||||
from lodgeit.controllers import BaseController
|
||||
from lodgeit.database import Paste
|
||||
from lodgeit.lib.xmlrpc import xmlrpc, exported
|
||||
from lodgeit.lib.highlighting import STYLES, LANGUAGES, get_style
|
||||
|
||||
|
||||
class XmlRpcController(BaseController):
|
||||
|
||||
def handle_request(self):
|
||||
if self.request.method == 'POST':
|
||||
return xmlrpc.handle_request(self.request)
|
||||
return render_template(self.request, 'xmlrpc.html',
|
||||
methods=xmlrpc.get_public_methods(),
|
||||
interface_url='http://%s/xmlrpc/' %
|
||||
self.request.environ['SERVER_NAME']
|
||||
)
|
||||
|
||||
@exported('pastes.newPaste')
|
||||
def pastes_new_paste(request, language, code, parent_id=None):
|
||||
"""Create a new paste."""
|
||||
paste = Paste(code, language, parent_id)
|
||||
request.dbsession.save(paste)
|
||||
request.dbsession.flush()
|
||||
return {
|
||||
'paste_id': paste.paste_id,
|
||||
'url': paste.url
|
||||
}
|
||||
|
||||
|
||||
@exported('pastes.getPaste')
|
||||
def pastes_get_paste(request, paste_id):
|
||||
"""Get all known information about a paste by a given paste id."""
|
||||
paste = request.dbsession.query(Paste).selectfirst(Paste.c.paste_id ==
|
||||
paste_id)
|
||||
if paste is None:
|
||||
return False
|
||||
return paste.to_dict()
|
||||
|
||||
|
||||
@exported('pastes.getRecent')
|
||||
def pastes_get_recent(request, amount=5):
|
||||
"""Return the last amount pastes."""
|
||||
amount = min(amount, 20)
|
||||
return [x.to_dict() for x in
|
||||
request.dbsession.query(Paste).select(
|
||||
order_by=[meta.desc(Paste.c.pub_date)],
|
||||
limit=amount
|
||||
)]
|
||||
|
||||
|
||||
@exported('pastes.getLast')
|
||||
def pastes_get_last(request):
|
||||
"""Get the most recent paste."""
|
||||
rv = pastes_get_recent(request, 1)
|
||||
if rv:
|
||||
return rv[0]
|
||||
return {}
|
||||
|
||||
|
||||
@exported('pastes.getLanguages')
|
||||
def pastes_get_languages(request):
|
||||
"""Get a list of supported languages."""
|
||||
return LANGUAGES.items()
|
||||
|
||||
|
||||
@exported('styles.getStyles')
|
||||
def styles_get_styles(request):
|
||||
"""Get a list of supported styles."""
|
||||
return STYLES.items()
|
||||
|
||||
|
||||
@exported('styles.getStylesheet')
|
||||
def styles_get_stylesheet(request, name):
|
||||
"""Return the stylesheet for a given style."""
|
||||
return get_style(name)
|
||||
|
||||
|
||||
controller = XmlRpcController
|
146
lodgeit/database.py
Normal file
146
lodgeit/database.py
Normal file
@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.database
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Database fun :)
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import time
|
||||
import difflib
|
||||
import sqlalchemy as meta
|
||||
from random import random
|
||||
from time import time
|
||||
from hashlib import sha1
|
||||
from datetime import datetime
|
||||
|
||||
from lodgeit.lib.highlighting import highlight, LANGUAGES
|
||||
|
||||
metadata = meta.MetaData()
|
||||
|
||||
|
||||
pastes = meta.Table('pastes', metadata,
|
||||
meta.Column('paste_id', meta.Integer, primary_key=True),
|
||||
meta.Column('code', meta.Unicode),
|
||||
meta.Column('parsed_code', meta.Unicode),
|
||||
meta.Column('parent_id', meta.Integer, meta.ForeignKey('pastes.paste_id'),
|
||||
nullable=True),
|
||||
meta.Column('pub_date', meta.DateTime),
|
||||
meta.Column('language', meta.Unicode(30)),
|
||||
meta.Column('user_hash', meta.Unicode(40), nullable=True),
|
||||
meta.Column('handled', meta.Boolean, nullable=False)
|
||||
)
|
||||
|
||||
|
||||
def generate_user_hash():
|
||||
return sha1('%s|%s' % (random(), time())).hexdigest()
|
||||
|
||||
|
||||
class Paste(object):
|
||||
|
||||
def __init__(self, code, language, parent=None, user_hash=None):
|
||||
if language not in LANGUAGES:
|
||||
raise ValueError('unsupported language %r' % language)
|
||||
self.code = code
|
||||
self.language = language
|
||||
self.rehighlight()
|
||||
if isinstance(parent, Paste):
|
||||
self.parent = parent
|
||||
elif parent is not None:
|
||||
self.parent_id = parent
|
||||
self.pub_date = datetime.now()
|
||||
self.handled = False
|
||||
self.user_hash = user_hash
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return '/show/%d' % self.paste_id
|
||||
|
||||
def compare_to(self, other, context_lines=4, template=False):
|
||||
udiff = u'\n'.join(difflib.unified_diff(
|
||||
self.code.splitlines(),
|
||||
other.code.splitlines(),
|
||||
fromfile='Paste #%d' % self.paste_id,
|
||||
tofile='Paste #%d' % other.paste_id,
|
||||
lineterm='',
|
||||
n=context_lines
|
||||
))
|
||||
if template:
|
||||
from lodgeit.lib.diff import prepare_udiff
|
||||
rv = prepare_udiff(udiff)
|
||||
return rv and rv[0] or None
|
||||
return udiff
|
||||
|
||||
def rehighlight(self):
|
||||
self.parsed_code = highlight(self.code, self.language)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'paste_id': self.paste_id,
|
||||
'code': self.code,
|
||||
'parsed_code': self.parsed_code,
|
||||
'pub_date': int(time.mktime(self.pub_date.timetuple())),
|
||||
'language': self.language,
|
||||
'parent_id': self.parent_id,
|
||||
'url': self.url
|
||||
}
|
||||
|
||||
def render_preview(self):
|
||||
try:
|
||||
start = self.parsed_code.index('</pre>')
|
||||
code = self.parsed_code[
|
||||
self.parsed_code.index('<pre>', start) + 5:
|
||||
self.parsed_code.rindex('</pre>')
|
||||
].strip('\n').splitlines()
|
||||
except IndexError:
|
||||
code = ''.strip('\n').splitlines()
|
||||
code = '\n'.join(code[:5] + ['...'])
|
||||
return '<pre class="syntax">%s</pre>' % code
|
||||
|
||||
@staticmethod
|
||||
def fetch_replies(req):
|
||||
"""
|
||||
Get the new replies for the owern of a request and flag them
|
||||
as handled.
|
||||
"""
|
||||
s = meta.select([pastes.c.paste_id],
|
||||
pastes.c.user_hash == req.user_hash
|
||||
)
|
||||
paste_list = req.dbsession.query(Paste).select(
|
||||
(Paste.c.parent_id.in_(s)) &
|
||||
(Paste.c.handled == False) &
|
||||
(Paste.c.user_hash != req.user_hash),
|
||||
order_by=[meta.desc(Paste.c.pub_date)]
|
||||
)
|
||||
to_mark = [p.paste_id for p in paste_list]
|
||||
req.engine.execute(pastes.update(pastes.c.paste_id.in_(*to_mark)),
|
||||
handled=True
|
||||
)
|
||||
return paste_list
|
||||
|
||||
@staticmethod
|
||||
def count(con):
|
||||
s = meta.select([meta.func.count(pastes.c.paste_id)])
|
||||
return con.execute(s).fetchone()[0]
|
||||
|
||||
@staticmethod
|
||||
def resolve_root(sess, paste_id):
|
||||
q = sess.query(Paste)
|
||||
while True:
|
||||
paste = q.selectfirst(Paste.c.paste_id == paste_id)
|
||||
if paste is None:
|
||||
return
|
||||
if paste.parent_id is None:
|
||||
return paste
|
||||
paste_id = paste.parent_id
|
||||
|
||||
|
||||
meta.mapper(Paste, pastes, properties={
|
||||
'children': meta.relation(Paste,
|
||||
primaryjoin=pastes.c.parent_id==pastes.c.paste_id,
|
||||
cascade='all',
|
||||
backref=meta.backref('parent', remote_side=[pastes.c.paste_id])
|
||||
)
|
||||
})
|
0
lodgeit/lib/__init__.py
Normal file
0
lodgeit/lib/__init__.py
Normal file
170
lodgeit/lib/diff.py
Normal file
170
lodgeit/lib/diff.py
Normal file
@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.lib.diff
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Render a nice diff between two things.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import re
|
||||
from lodgeit.application import jinja_environment
|
||||
from cgi import escape
|
||||
|
||||
|
||||
def prepare_udiff(udiff):
|
||||
"""
|
||||
Prepare an udiff for a template
|
||||
"""
|
||||
renderer = DiffRenderer(udiff)
|
||||
return renderer.prepare()
|
||||
|
||||
|
||||
class DiffRenderer(object):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
self.lines = [escape(line) for line in udiff.splitlines()]
|
||||
|
||||
def _extract_rev(self, line1, line2):
|
||||
try:
|
||||
if line1.startswith('--- ') and line2.startswith('+++ '):
|
||||
filename, old_rev = line1[4:].split(None, 1)
|
||||
new_rev = line2[4:].split(None, 1)[1]
|
||||
return filename, 'Old', 'New'
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
return None, None, None
|
||||
|
||||
def _highlight_line(self, line, next):
|
||||
"""
|
||||
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]:
|
||||
start += 1
|
||||
end = -1
|
||||
limit -= start
|
||||
while -end <= limit and line['line'][end] == next['line'][end]:
|
||||
end -= 1
|
||||
end += 1
|
||||
if start or end:
|
||||
def do(l):
|
||||
last = end + len(l['line'])
|
||||
if l['action'] == 'add':
|
||||
tag = 'ins'
|
||||
else:
|
||||
tag = 'del'
|
||||
l['line'] = u'%s<%s>%s</%s>%s' % (
|
||||
l['line'][:start],
|
||||
tag,
|
||||
l['line'][start:last],
|
||||
tag,
|
||||
l['line'][last:]
|
||||
)
|
||||
do(line)
|
||||
do(next)
|
||||
|
||||
def _parse_udiff(self):
|
||||
"""
|
||||
Parse the diff an return data for the template.
|
||||
"""
|
||||
lineiter = iter(self.lines)
|
||||
files = []
|
||||
try:
|
||||
line = lineiter.next()
|
||||
while True:
|
||||
# continue until we found the old file
|
||||
if not line.startswith('--- '):
|
||||
line = lineiter.next()
|
||||
continue
|
||||
|
||||
chunks = []
|
||||
filename, old_rev, new_rev = \
|
||||
self._extract_rev(line, lineiter.next())
|
||||
files.append({
|
||||
'filename': filename,
|
||||
'old_revision': old_rev,
|
||||
'new_revision': new_rev,
|
||||
'chunks': chunks
|
||||
})
|
||||
|
||||
line = lineiter.next()
|
||||
while line:
|
||||
match = self._chunk_re.match(line)
|
||||
if not match:
|
||||
break
|
||||
|
||||
lines = []
|
||||
chunks.append(lines)
|
||||
|
||||
old_line, old_end, new_line, new_end = \
|
||||
[int(x or 1) for x in match.groups()]
|
||||
old_line -= 1
|
||||
new_line -= 1
|
||||
old_end += old_line
|
||||
new_end += new_line
|
||||
line = lineiter.next()
|
||||
|
||||
while old_line < old_end or new_line < new_end:
|
||||
if line:
|
||||
command, line = line[0], line[1:]
|
||||
else:
|
||||
command = ' '
|
||||
affects_old = affects_new = False
|
||||
|
||||
if command == ' ':
|
||||
affects_old = affects_new = True
|
||||
action = 'unmod'
|
||||
elif command == '+':
|
||||
affects_new = True
|
||||
action = 'add'
|
||||
elif command == '-':
|
||||
affects_old = True
|
||||
action = 'del'
|
||||
else:
|
||||
raise RuntimeError()
|
||||
|
||||
old_line += affects_old
|
||||
new_line += affects_new
|
||||
lines.append({
|
||||
'old_lineno': affects_old and old_line or u'',
|
||||
'new_lineno': affects_new and new_line or u'',
|
||||
'action': action,
|
||||
'line': line
|
||||
})
|
||||
line = lineiter.next()
|
||||
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
# highlight inline changes
|
||||
for file in files:
|
||||
for chunk in chunks:
|
||||
lineiter = iter(chunk)
|
||||
first = True
|
||||
try:
|
||||
while True:
|
||||
line = lineiter.next()
|
||||
if line['action'] != 'unmod':
|
||||
nextline = lineiter.next()
|
||||
if nextline['action'] == 'unmod' or \
|
||||
nextline['action'] == line['action']:
|
||||
continue
|
||||
self._highlight_line(line, nextline)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
return files
|
||||
|
||||
def prepare(self):
|
||||
return self._parse_udiff()
|
81
lodgeit/lib/highlighting.py
Normal file
81
lodgeit/lib/highlighting.py
Normal file
@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.lib.highlighting
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Highlighting helpers.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import pygments
|
||||
from pygments.util import ClassNotFound
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.styles import get_all_styles
|
||||
from pygments.formatters import HtmlFormatter
|
||||
|
||||
|
||||
#: we use a hardcoded list here because we want to keep the interface
|
||||
#: simple
|
||||
LANGUAGES = {
|
||||
'text': 'Text',
|
||||
'python': 'Python',
|
||||
'pycon': 'Python Console Sessions',
|
||||
'pytb': 'Python Tracebacks',
|
||||
'html+php': 'PHP',
|
||||
'html+django': 'Django / Jinja Templates',
|
||||
'html+mako': 'Mako Templates',
|
||||
'html+myghty': 'Myghty Templates',
|
||||
'apache': 'Apache Config (.htaccess)',
|
||||
'bash': 'Bash',
|
||||
'bat': 'Batch (.bat)',
|
||||
'c': 'C',
|
||||
'cpp': 'C++',
|
||||
'csharp': 'C#',
|
||||
'css': 'CSS',
|
||||
'smarty': 'Smarty',
|
||||
'html+php': 'PHP',
|
||||
'html+genshi': 'Genshi Templates',
|
||||
'js': 'JavaScript',
|
||||
'java': 'Java',
|
||||
'jsp': 'JSP',
|
||||
'lua': 'Lua',
|
||||
'haskell': 'Haskell',
|
||||
'scheme': 'Scheme',
|
||||
'ruby': 'Ruby',
|
||||
'rhtml': 'eRuby / rhtml',
|
||||
'tex': 'TeX / LaTeX',
|
||||
'xml': 'XML'
|
||||
}
|
||||
|
||||
STYLES = dict((x, x.title()) for x in get_all_styles())
|
||||
|
||||
|
||||
def highlight(code, language):
|
||||
"""
|
||||
Highlight a given code to HTML
|
||||
"""
|
||||
lexer = get_lexer_by_name(language)
|
||||
return pygments.highlight(code, lexer, formatter)
|
||||
|
||||
|
||||
def get_style(request):
|
||||
"""
|
||||
Style for a given request
|
||||
"""
|
||||
if isinstance(request, basestring):
|
||||
style_name = request
|
||||
else:
|
||||
style_name = request.cookies.get('style')
|
||||
if style_name:
|
||||
style_name = style_name.value
|
||||
else:
|
||||
style_name = 'pastie'
|
||||
try:
|
||||
f = HtmlFormatter(style=style_name)
|
||||
except ClassNotFound:
|
||||
return style_name, ''
|
||||
return style_name, f.get_style_defs(('#paste', '.syntax'))
|
||||
|
||||
|
||||
formatter = HtmlFormatter(linenos=True, cssclass='syntax', style='pastie')
|
84
lodgeit/lib/pagination.py
Normal file
84
lodgeit/lib/pagination.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.lib.pagination
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Fancy Pagination.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import math
|
||||
|
||||
|
||||
def generate_pagination(page, per_page, total, link_builder=None,
|
||||
normal='<a href="%(url)s">%(page)d</a>',
|
||||
active='<strong>%(page)d</strong>',
|
||||
commata=',\n', ellipsis=' ...\n', threshold=3,
|
||||
prev_link=True, next_link=True,
|
||||
gray_prev_link=True, gray_next_link=True):
|
||||
"""
|
||||
Generates a pagination.
|
||||
|
||||
:param page: current page number
|
||||
:param per_page: items per page
|
||||
:param total: total number of items
|
||||
:param link_builder: a function which is called with a page number
|
||||
and has to return the link to a page. Per
|
||||
default it links to ``?page=$PAGE``
|
||||
:param normal: template for normal (not active) link
|
||||
:param active: template for active link
|
||||
:param commata: inserted into the output to separate two links
|
||||
:param ellipsis: inserted into the output to display an ellipsis
|
||||
:param threshold: number of links next to each node (left end,
|
||||
right end and current page)
|
||||
:param prev_link: display back link
|
||||
:param next_link: dipslay next link
|
||||
:param gray_prev_link: the back link is displayed as span class disabled
|
||||
if no backlink exists. otherwise it's not
|
||||
displayed at all
|
||||
:param gray_next_link: like `gray_prev_link` just for the next page link
|
||||
"""
|
||||
page = int(page or 1)
|
||||
if link_builder is None:
|
||||
link_builder = lambda page: '?page=%d' % page
|
||||
|
||||
was_ellipsis = False
|
||||
result = []
|
||||
pages = int(math.ceil(total / float(per_page)))
|
||||
prev = None
|
||||
next = None
|
||||
for num in xrange(1, pages + 1):
|
||||
if num - 1 == page:
|
||||
next = num
|
||||
if num + 1 == page:
|
||||
prev = num
|
||||
if num <= threshold or num > pages - threshold or \
|
||||
abs(page - num) < math.ceil(threshold / 2.0):
|
||||
if result and result[-1] != ellipsis:
|
||||
result.append(commata)
|
||||
was_space = False
|
||||
link = link_builder(num)
|
||||
template = num == page and active or normal
|
||||
result.append(template % {
|
||||
'url': link,
|
||||
'page': num
|
||||
})
|
||||
elif not was_ellipsis:
|
||||
was_ellipsis = True
|
||||
result.append(ellipsis)
|
||||
|
||||
if next_link:
|
||||
if next is not None:
|
||||
result.append(u' <a href="%s">Next »</a>' %
|
||||
link_builder(next))
|
||||
elif gray_next_link:
|
||||
result.append(u' <span class="disabled">Next »</span>')
|
||||
if prev_link:
|
||||
if prev is not None:
|
||||
result.insert(0, u'<a href="%s">« Prev</a> ' %
|
||||
link_builder(prev))
|
||||
elif gray_prev_link:
|
||||
result.insert(0, u'<span class="disabled">« Prev</span> ')
|
||||
|
||||
return u''.join(result)
|
60
lodgeit/lib/xmlrpc.py
Normal file
60
lodgeit/lib/xmlrpc.py
Normal file
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.lib.xmlrpc
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
XMLRPC helper stuff.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
import inspect
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
|
||||
|
||||
from lodgeit.application import Response
|
||||
|
||||
|
||||
class XMLRPCRequestHandler(SimpleXMLRPCDispatcher):
|
||||
|
||||
def __init__(self):
|
||||
SimpleXMLRPCDispatcher.__init__(self, True, 'utf-8')
|
||||
|
||||
def handle_request(self, request):
|
||||
def dispatch(method_name, params):
|
||||
method = self.funcs[method_name]
|
||||
if method_name.startswith('system.'):
|
||||
return method(*params)
|
||||
return method(request, *params)
|
||||
response = self._marshaled_dispatch(request.data, dispatch)
|
||||
return Response(response, mimetype='text/xml')
|
||||
|
||||
def get_public_methods(self):
|
||||
if not hasattr(self, '_public_methods'):
|
||||
result = []
|
||||
for name, f in self.funcs.iteritems():
|
||||
if name.startswith('system.'):
|
||||
continue
|
||||
args, varargs, varkw, defaults = inspect.getargspec(f)
|
||||
result.append({
|
||||
'name': name,
|
||||
'doc': inspect.getdoc(f) or '',
|
||||
'signature': inspect.formatargspec(
|
||||
args, varargs, varkw, defaults,
|
||||
formatvalue=lambda o: '=' + repr(o)
|
||||
)
|
||||
})
|
||||
result.sort(key=lambda x: x['name'].lower())
|
||||
self._public_methods = result
|
||||
return self._public_methods
|
||||
|
||||
|
||||
xmlrpc = XMLRPCRequestHandler()
|
||||
xmlrpc.register_introspection_functions()
|
||||
|
||||
|
||||
def exported(name):
|
||||
"""Make a function external available via xmlrpc."""
|
||||
def proxy(f):
|
||||
xmlrpc.register_function(f, name)
|
||||
return f
|
||||
return proxy
|
21
lodgeit/static/cookie.js
Normal file
21
lodgeit/static/cookie.js
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* add very basic Cookie features to jquery
|
||||
*/
|
||||
|
||||
jQuery.cookie = function(name, value) {
|
||||
if (typeof value != 'undefined') {
|
||||
document.cookie = name + '=' + encodeURIComponent(value);
|
||||
}
|
||||
else {
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
return decodeURIComponent(cookie.substring(name.length + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
2245
lodgeit/static/jquery.js
vendored
Normal file
2245
lodgeit/static/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
127
lodgeit/static/lodgeit.js
Normal file
127
lodgeit/static/lodgeit.js
Normal file
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* LodgeIt JavaScript Module
|
||||
*
|
||||
* addes fancy and annoying javascript effects to that page
|
||||
* but hey. now it's web2.0!!!!111
|
||||
*/
|
||||
var LodgeIt = {
|
||||
|
||||
init : function() {
|
||||
/**
|
||||
* make textarea 1px height and save the value for resizing
|
||||
* in a variable.
|
||||
*/
|
||||
var textarea = $('textarea');
|
||||
var submitform = $('form.submitform');
|
||||
var textareaHeight = $.cookie('ta_height');
|
||||
if (textareaHeight) {
|
||||
textareaHeight = parseInt(textareaHeight);
|
||||
}
|
||||
else {
|
||||
textareaHeight = textarea.height();
|
||||
}
|
||||
submitform.hide();
|
||||
textarea.css('height', '1px');
|
||||
|
||||
/**
|
||||
* links marked with "autoclose" inside the related div
|
||||
* use some little magic to get an auto hide animation on
|
||||
* click, before the actual request is sent to the browser.
|
||||
*/
|
||||
$('div.related div.content a.autoclose').each(function() {
|
||||
this.onclick = function() {
|
||||
var href = this.getAttribute('href');
|
||||
$('div.related div.content').slideUp(300, function() {
|
||||
document.location.href = href;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* and here we do something similar for the forms. block
|
||||
* submitting until the close animation is done.
|
||||
*/
|
||||
$('div.related form').each(function() {
|
||||
var submit = false;
|
||||
var self = this;
|
||||
this.onsubmit = function() {
|
||||
if (submit)
|
||||
return true;
|
||||
$('div.related div.content').slideUp(300, function() {
|
||||
submit = true;
|
||||
self.submit();
|
||||
});
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* now where everything is done resize the textarea
|
||||
* we do this at the end to speed things up on slower systems
|
||||
* this code is only used for the frontpage.
|
||||
*/
|
||||
textarea.animate({
|
||||
height: textareaHeight
|
||||
}, textareaHeight * 1.2, 'linear', function() {
|
||||
textarea[0].focus();
|
||||
});
|
||||
submitform.fadeIn(textareaHeight, function() {
|
||||
// small workaround in order to not slow firefox down
|
||||
submitform.css('opacity', 'inherit');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* slide-toggle the related links box
|
||||
*/
|
||||
toggleRelatedBox : function() {
|
||||
$('div.related div.content').slideToggle(500);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Textarea resizer helper
|
||||
*/
|
||||
resizeTextarea : function(step) {
|
||||
var textarea = $('textarea');
|
||||
var oldHeight = textarea.height();
|
||||
var newHeight = oldHeight + step;
|
||||
if (newHeight >= 100) {
|
||||
$.cookie('ta_height', newHeight);
|
||||
textarea.animate({
|
||||
height: newHeight
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* hide the notification box
|
||||
*/
|
||||
hideNotification : function() {
|
||||
$('div.notification').slideUp(300);
|
||||
},
|
||||
|
||||
/**
|
||||
* remove user hash cookie
|
||||
*/
|
||||
removeCookie : function() {
|
||||
if (confirm('Do really want to remove your cookie?')) {
|
||||
$.cookie('user_hash', '');
|
||||
alert('Your cookie was resetted!');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(LodgeIt.init);
|
321
lodgeit/static/style.css
Normal file
321
lodgeit/static/style.css
Normal file
@ -0,0 +1,321 @@
|
||||
/**
|
||||
* New Lodge It Style
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
div.page {
|
||||
margin: 30px;
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
margin: 0;
|
||||
color: #cd0000;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
margin: -5px 0 20px 20px;
|
||||
color: #e18f00;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
margin: 20px 0 0 0;
|
||||
color: #cd0000;
|
||||
}
|
||||
|
||||
h2 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #cd0000;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #e18f00;
|
||||
}
|
||||
|
||||
div.text {
|
||||
max-width: 700px;
|
||||
text-align: justify;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#navigation {
|
||||
float: right;
|
||||
list-style: none;
|
||||
margin: -50px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#navigation li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#navigation li a {
|
||||
display: block;
|
||||
padding: 5px 10px 5px 10px;
|
||||
background-color: #333;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#navigation li a:hover {
|
||||
background-color: #f89e00;
|
||||
}
|
||||
|
||||
#navigation li.active a {
|
||||
background-color: #cd0000;
|
||||
}
|
||||
|
||||
#paste {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
#paste table, #paste tbody {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
#paste td.linenos {
|
||||
background-color: #333;
|
||||
padding-right: 5px;
|
||||
padding-left: 20px;
|
||||
text-align: right;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
#paste td.code {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#paste pre {
|
||||
margin: 0;
|
||||
padding: 5px 0 5px 0;
|
||||
font-family: 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.related,
|
||||
div.notification {
|
||||
margin: 0 0 10px 0;
|
||||
border: 1px solid #cd0000;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
div.related h3,
|
||||
div.notification h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #cd0000;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
div.notification h3,
|
||||
div.related h3 a {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.related h3 a:hover {
|
||||
background-color: #c41200;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.related h3 a:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.related p,
|
||||
div.notification p {
|
||||
padding: 5px 10px 5px 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related a,
|
||||
div.notification a {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
div.related a:hover,
|
||||
div.notification a:hover {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0 0 10px 30px;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
div.related div.content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
border: 1px solid #333;
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
font-size: 15px;
|
||||
color: black;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
input[type="submit"],
|
||||
input[type="button"] {
|
||||
background-color: #cd0000;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table.diff {
|
||||
width: 100%;
|
||||
border: 2px solid #ccc;
|
||||
border-collapse: collapse;
|
||||
empty-cells: show;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table.diff th.old_rev,
|
||||
table.diff th.new_rev {
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
table.diff tr.line th.old_rev,
|
||||
table.diff tr.line th.new_rev {
|
||||
padding: 0.2em 0.5em 0.2em 0;
|
||||
text-align: right;
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
background-color: #eee;
|
||||
color: #444;
|
||||
border: 1px solid #ccc;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.diff tr.ellipsis th {
|
||||
border: 1px solid #ccc;
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
|
||||
table.diff tr.line td.code {
|
||||
padding: 0.1em 0.4em 0.1em 0.4em;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
border: 1px solid #ddd;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
table.diff tr.add td.code {
|
||||
background-color: #dfd;
|
||||
}
|
||||
|
||||
table.diff tr.del td.code {
|
||||
background-color: #fcc;
|
||||
}
|
||||
|
||||
table.diff tr.add td.code ins {
|
||||
background-color: #9e9;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table.diff tr.del td.code del {
|
||||
background-color: #e99;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul.paste_list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul.paste_list p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.paste_list pre {
|
||||
margin: 4px 10px 4px 30px;
|
||||
padding: 4px;
|
||||
font-family: 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
ul.paste_list li {
|
||||
margin: 10px 0 0 0;
|
||||
padding: 5px 0 5px 10px;
|
||||
}
|
||||
|
||||
ul.paste_list li.even {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
ul.paste_list li.odd {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
div.pagination {
|
||||
margin: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
ul.paste_tree {
|
||||
margin: 0 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.paste_tree li {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.paste_tree li.highlighted {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
ul.paste_tree li.highlighted ul {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
ul.paste_tree li.highlighted > a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.paste_tree ul {
|
||||
padding: 0 0 0 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul.paste_tree,
|
||||
ul.paste_tree ul {
|
||||
list-style: circle;
|
||||
}
|
34
lodgeit/urls.py
Normal file
34
lodgeit/urls.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.urls
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The URL mapping.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
from wsgitk.routing import automap
|
||||
|
||||
|
||||
@automap
|
||||
def urlmap():
|
||||
# paste interface
|
||||
root > 'pastes/new_paste'
|
||||
root / 'show' / int('paste_id') > 'pastes/show_paste'
|
||||
root / 'compare' / int('new_id') / int('old_id') > 'pastes/compare_paste'
|
||||
root / 'tree' / int('paste_id') > 'pastes/show_tree'
|
||||
|
||||
# paste list
|
||||
root / 'all' > 'pastes/show_all'
|
||||
root / 'all' / int('page') > 'pastes/show_all'
|
||||
|
||||
# xmlrpc
|
||||
root / 'xmlrpc' > 'xmlrpc/handle_request'
|
||||
|
||||
# static pages
|
||||
root / 'about' > 'static/about'
|
||||
|
||||
# redirect pages
|
||||
root / 'compare' > 'pastes/compare_paste'
|
||||
root / 'colorscheme' > 'pastes/set_colorscheme'
|
62
lodgeit/views/about.html
Normal file
62
lodgeit/views/about.html
Normal file
@ -0,0 +1,62 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'About LodgeIt' %}
|
||||
{% set active_page = 'about' %}
|
||||
{% block body %}
|
||||
<div class="text">
|
||||
<h3 id="why-the-hell-another-pastebin">Why the hell another pastebin?</h3>
|
||||
<p>
|
||||
Good question. Basically the world doesn't need another pastebin.
|
||||
There is <a href="http://pastie.caboo.se/">pastie</a> and
|
||||
<a href="http://dpaste.com/">dpaste.com</a> which
|
||||
both use kick-ass highlighting libraries for highlighting the
|
||||
code and both have an initiutive user interface. Nevertheless there
|
||||
are some features which are unique to lodgeit.
|
||||
</p>
|
||||
<h3 id="features">Features</h3>
|
||||
<ul>
|
||||
<li>clean user interface</li>
|
||||
<li>different color schemes for the sourcecode</li>
|
||||
<li>reply to pastes</li>
|
||||
<li>diffs of different pastes</li>
|
||||
<li>support for many python template languages</li>
|
||||
<li>support for many scripting languages like Python and Ruby, even with
|
||||
weird syntax (ruby *cough*)</li>
|
||||
<li><a href="/xmlrpc/">XMLRPC support</a></li>
|
||||
<li>persistent pastes</li>
|
||||
<li>reply notification</li>
|
||||
<li>valid HTML 4.0</li>
|
||||
</ul>
|
||||
<h3 id="request-more-languages">Request More Languages</h3>
|
||||
<p>
|
||||
A language is missing in the list? File a ticket in the
|
||||
<a href="http://trac.pocoo.org/">pocoo trac</a> and we add that as soon
|
||||
as possible.
|
||||
</p>
|
||||
<h3 id="software-used">Software Used</h3>
|
||||
<ul>
|
||||
<li><a href="http://www.sqlite.org/">sqlite3</a> as database</li>
|
||||
<li><a href="http://pygments.pocoo.org/">pygments</a> for syntax highlighting</li>
|
||||
<li><a href="http://www.python.org/">python</a> as scripting language</li>
|
||||
<li><a href="http://jinja.pocoo.org/">Jinja</a> for templating</li>
|
||||
<li><a href="http://wsgitk.pocoo.org/">wsgitk</a> for the WSGI implementation</li>
|
||||
<li><a href="http://www.sqlalchemy.org">SQLAlchemy</a> as database layer</li>
|
||||
<li><a href="http://www.jquery.com/">jQuery</a> for scripting</li>
|
||||
</ul>
|
||||
<h3 id="who">Who?</h3>
|
||||
<p>
|
||||
<a href="http://lucumr.pocoo.org/">mitsuhiko</a> from the pocoo
|
||||
team is responsible for the pastebin. Pygments is a pocoo project
|
||||
led by Georg Brandl.
|
||||
</p>
|
||||
<h3 id="piracy">Piracy</h3>
|
||||
<p>
|
||||
LodgeIt does not use user accounts because it's logging in for using a
|
||||
pastebin is useles. However this pastebin creates unique user ids for you
|
||||
and for 31 days. Whenever you return to the pastebin it will notify you
|
||||
about replies to your pastes. If you don't want to have that feature you
|
||||
can let lodgeit forget about you by
|
||||
<a href="javascript:LodgeIt.removeCookie()">removing the cookie</a>.
|
||||
Please note that on the next request you will get a new id.
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
29
lodgeit/views/compare_paste.html
Normal file
29
lodgeit/views/compare_paste.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'Compare Pastes' %}
|
||||
{% block body %}
|
||||
<p>
|
||||
Differences between the pastes
|
||||
<a href="{{ old.url|e }}">#{{ old.paste_id }}</a> ({{ old.pub_date|datetimeformat }})
|
||||
and <a href="{{ new.url|e }}">#{{ new.paste_id }}</a> ({{ new.pub_date|datetimeformat }}).
|
||||
</p>
|
||||
{% if diff.chunks %}
|
||||
<table class="diff">
|
||||
{%- for chunk in diff.chunks -%}
|
||||
{% if not loop.first -%}
|
||||
<tr class="ellipsis">
|
||||
<th colspan="3">...</th>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
{% for line in chunk %}
|
||||
<tr class="line {{ line.action }}">
|
||||
<th class="old_rev">{{ line.old_lineno }}</th>
|
||||
<th class="new_rev">{{ line.new_lineno }}</th>
|
||||
<td class="code">{{ line.line }}</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<p>The two pastes are identical.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
62
lodgeit/views/layout.html
Normal file
62
lodgeit/views/layout.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ page_title|e }} | LodgeIt!</title>
|
||||
<link rel="stylesheet" href="/static/style.css" type="text/css">
|
||||
<script type="text/javascript" src="/static/jquery.js"></script>
|
||||
<script type="text/javascript" src="/static/cookie.js"></script>
|
||||
<script type="text/javascript" src="/static/lodgeit.js"></script>
|
||||
{%- if css %}
|
||||
<style type="text/css">
|
||||
{{ css|e }}
|
||||
</style>
|
||||
{%- endif %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div id="header"><h1>Lodge It</h1></div>
|
||||
<ul id="navigation">
|
||||
{%- for href, id, caption in [
|
||||
('/', 'new', 'New'),
|
||||
('/all/', 'all', 'All'),
|
||||
('/about', 'about', 'About')
|
||||
] %}
|
||||
<li{% if active_page == id %} class="active"{%
|
||||
endif %}><a href="{{ href|e }}">{{ caption|e }}</a></li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
<div class="content">
|
||||
<h2>{{ page_title|e }}</h2>
|
||||
{%- if new_replies %}
|
||||
<div class="notification">
|
||||
<h3>Someone Replied To Your Paste</h3>
|
||||
{% for paste in new_replies %}
|
||||
<p>
|
||||
on {{ paste.pub_date|datetimeformat }} someone replied to
|
||||
your paste <a href="{{ paste.parent.url|e }}">#{{ paste.parent.paste_id }}</a>,
|
||||
in paste <a href="{{ paste.url|e }}">#{{ paste.paste_id }}</a>. Click here to
|
||||
<a href="/compare/{{ paste.paste_id }}/{{ paste.parent.paste_id }}">compare
|
||||
those two pastes</a>.
|
||||
</p>
|
||||
{% endfor %}
|
||||
<p><a href="javascript:LodgeIt.hideNotification()">hide this notification</a></p>
|
||||
</div>
|
||||
{% elif request.first_visit %}
|
||||
<div class="notification">
|
||||
<h3>Welcome On LodgeIt</h3>
|
||||
<p>
|
||||
Welcome on the LodgeIt pastebin. In order to use the notification feature
|
||||
a 31 day cookie with an unique ID was created for you. The lodgeit database
|
||||
does not store any information about you, it's just used for an advanced
|
||||
pastebin experience :-). Read more on the <a href="/about#piracy">about
|
||||
lodgeit</a> page. Have fun :-)
|
||||
</p>
|
||||
<p><a href="javascript:LodgeIt.hideNotification()">hide this notification</a></p>
|
||||
</div>
|
||||
{% endif -%}
|
||||
{% block body %}{% endblock -%}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
20
lodgeit/views/new_paste.html
Normal file
20
lodgeit/views/new_paste.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'New Paste' %}
|
||||
{% set active_page = 'new' %}
|
||||
{% block body %}
|
||||
<form action="/" method="post" class="submitform">
|
||||
{% if parent %}
|
||||
<input type="hidden" name="parent" value="{{ parent.paste_id }}">
|
||||
{% endif %}
|
||||
<textarea name="code" rows="10" cols="80">{{ parent.code|e }}</textarea>
|
||||
<select name="language">
|
||||
{% for key, caption in languages|dictsort(true, 'value') -%}
|
||||
<option value="{{ key }}"{% if parent.language == key
|
||||
%} selected="selected"{% endif %}>{{ caption|e }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Paste!">
|
||||
<input type="button" value="▲" onclick="LodgeIt.resizeTextarea(-100)">
|
||||
<input type="button" value="▼" onclick="LodgeIt.resizeTextarea(100)">
|
||||
</form>
|
||||
{% endblock %}
|
16
lodgeit/views/not_found.html
Normal file
16
lodgeit/views/not_found.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'Page Not Found' %}
|
||||
{% block body %}
|
||||
<p>
|
||||
Sorry, but the page you requested was not found on this server.
|
||||
</p>
|
||||
<p>
|
||||
We've recently updated this pastebin. While it is out goal for nothing to get
|
||||
lost, you may have found a page that was mis-placed. Check your URL to ensure
|
||||
you have gone where you intended. If everything looks OK and you still see
|
||||
this error page, please consider <a href="/about">conacting us</a>.
|
||||
</p>
|
||||
<p>
|
||||
Click <a href="/">here</a> to add a new paste.
|
||||
</p>
|
||||
{% endblock %}
|
21
lodgeit/views/paste_tree.html
Normal file
21
lodgeit/views/paste_tree.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'Paste Tree' %}
|
||||
{% set active_page = 'all' %}
|
||||
{% block body %}
|
||||
<p>
|
||||
Here you can see the requested tree of paste replies. The paste you're
|
||||
coming from is highlighted.
|
||||
</p>
|
||||
<ul class="paste_tree">
|
||||
{%- for paste in [paste] recursive %}
|
||||
<li{% if paste.paste_id == current
|
||||
%} class="highlighted"{% endif %}><a href="{{ paste.url|e
|
||||
}}">Paste #{{ paste.paste_id }}</a> — {{
|
||||
paste.pub_date|datetimeformat }}
|
||||
{%- if paste.children -%}
|
||||
<ul>{{ loop(paste.children) }}</ul>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
16
lodgeit/views/show_all.html
Normal file
16
lodgeit/views/show_all.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'All Pastes' %}
|
||||
{% set active_page = 'all' %}
|
||||
{% block body %}
|
||||
<ul class="paste_list">
|
||||
{% for paste in pastes %}
|
||||
<li class="{% cycle 'even', 'odd' %}"><p><a href="{{ paste.url|e
|
||||
}}">Paste #{{ paste.paste_id }}</a>,
|
||||
pasted on {{ paste.pub_date|datetimeformat }}</p>
|
||||
{{ paste.render_preview() }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
<div class="pagination">
|
||||
{{ pagination }}
|
||||
</div>
|
||||
{% endblock %}
|
48
lodgeit/views/show_paste.html
Normal file
48
lodgeit/views/show_paste.html
Normal file
@ -0,0 +1,48 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'Paste #%d'|format(paste.paste_id) %}
|
||||
{% set active_page = 'all' %}
|
||||
{% block body %}
|
||||
<div class="related">
|
||||
<h3><a href="javascript:LodgeIt.toggleRelatedBox()">Paste Details</a></h3>
|
||||
<div class="content">
|
||||
<p>posted on {{ paste.pub_date|datetimeformat }}</p>
|
||||
<ul>
|
||||
<li><a class="autoclose" href="/?reply_to={{ paste.paste_id }}">reply to this paste</a></li>
|
||||
{% if paste.parent %}
|
||||
<li><a class="autoclose" href="/compare/{{ paste.paste_id }}/{{
|
||||
paste.parent.paste_id }}">compare it with the parent paste</a></li>
|
||||
<li><a class="autoclose" href="{{ paste.parent.url|e }}">look at the parent paste</a></li>
|
||||
{% endif %}
|
||||
{% if paste.children %}
|
||||
<li>the following pastes replied to this paste:
|
||||
{% for child in paste.children %}
|
||||
<a class="autoclose" href="{{ child.url|e }}">#{{ child.paste_id }}</a>
|
||||
{%- if not loop.last %},{% endif -%}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if paste.parent or paste.children %}
|
||||
<li><a href="/tree/{{ paste.paste_id }}">show paste tree</a></li>
|
||||
{% endif %}
|
||||
<li>compare with paste <form action="/compare" method="post">
|
||||
<input type="hidden" name="old" value="{{ paste.paste_id }}">
|
||||
<input type="text" name="new" value="#">
|
||||
<input type="submit" value="compare">
|
||||
</form></li>
|
||||
<li>select different colorscheme <form action="/colorscheme" method="post">
|
||||
<select name="style">
|
||||
{% for key, caption in styles|dictsort %}
|
||||
<option value="{{ key }}"{% if key == style
|
||||
%} selected="selected"{% endif %}>{{ caption }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="change">
|
||||
</form></li>
|
||||
<li><a href="javascript:LodgeIt.toggleLineNumbers()">toggle line numbers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="paste">
|
||||
{{ paste.parsed_code }}
|
||||
</div>
|
||||
{% endblock %}
|
25
lodgeit/views/xmlrpc.html
Normal file
25
lodgeit/views/xmlrpc.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = "XMLRPC" %}
|
||||
{% set active_page = 'about' %}
|
||||
{% block body %}
|
||||
<h3>Connecting To The XMLRPC Interface</h3>
|
||||
<p>
|
||||
The XMLRPC Interface is available at
|
||||
<tt>{{ interface_url|escape }}</tt>
|
||||
</p>
|
||||
<p>
|
||||
From python you can connect to it using the following
|
||||
sourcecode:
|
||||
</p>
|
||||
<pre>from xmlrpclib import ServerProxy
|
||||
s = ServerProxy('{{ interface_url|escape }}')
|
||||
s.pastes.method('parameter')</pre>
|
||||
<h3>Public Methods</h3>
|
||||
<ul>
|
||||
{% for method in methods %}
|
||||
<li><strong>{{ method.name|escape }}</strong>
|
||||
<em>{{ method.signature|escape }}</em>
|
||||
<p>{{ method.doc|e }}</p></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
@ -190,7 +190,7 @@ def download_paste(uid):
|
||||
paste = xmlrpc.pastes.getPaste(uid)
|
||||
if not paste:
|
||||
fail('Paste "%s" does not exist' % uid, 5)
|
||||
print paste['code']
|
||||
print paste['code'].encode('utf-8')
|
||||
|
||||
|
||||
def create_paste(code, title, author, language, private, tags):
|
||||
|
Loading…
Reference in New Issue
Block a user