[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)
|
paste = xmlrpc.pastes.getPaste(uid)
|
||||||
if not paste:
|
if not paste:
|
||||||
fail('Paste "%s" does not exist' % uid, 5)
|
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):
|
def create_paste(code, title, author, language, private, tags):
|
||||||
|
Loading…
Reference in New Issue
Block a user