diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | db.py | 32 | ||||
-rw-r--r-- | pastepy.py | 137 | ||||
-rw-r--r-- | static/edit.js | 83 | ||||
-rw-r--r-- | static/paste.css | 18 | ||||
-rw-r--r-- | templates/__init__.py | 3 | ||||
-rw-r--r-- | templates/base.tmpl | 22 | ||||
-rw-r--r-- | templates/paste.tmpl | 28 | ||||
-rw-r--r-- | templates/view.tmpl | 22 |
9 files changed, 350 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb0a79c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.*.swp +*.pyc +/settings.py +/db +templates/*.py @@ -0,0 +1,32 @@ +from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, Index +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker, relation, backref + +engine = create_engine('sqlite:////home/snakebite/pastepy/db') + +Base = declarative_base(engine = engine) + +class Paste(Base): + __tablename__ = 'paste' + + id = Column(Integer, primary_key = True) + hash = Column(String, unique = True, index = True) + nick = Column(String) + date = Column(DateTime, nullable = False) + syntax = Column(String) + title = Column(String) + text = Column(Text) + + def __init__(self, hash, nick, date, syntax, title, text): + self.nick = nick + self.hash = hash + self.date = date + self.syntax = syntax + self.title = title + self.text = text + + def __repr__(self): + return '<Paste(%d, "%s", "%s", "%s", "%s")>' % (self.id, self.hash, self.nick, self.date.ctime(), self.title) + +Base.metadata.create_all() +Session = sessionmaker(bind = engine, autoflush = True, autocommit = False) diff --git a/pastepy.py b/pastepy.py new file mode 100644 index 0000000..b3fc980 --- /dev/null +++ b/pastepy.py @@ -0,0 +1,137 @@ +import templates, pygments, cgi, db, datetime, hashlib, settings, os, mimetypes +from pygments import highlight +from pygments.lexers import get_all_lexers, get_lexer_by_name +from pygments.formatters import HtmlFormatter + +class Paste(object): + def __init__(self): + lexers = dict([(x[0], x[1][0]) for x in get_all_lexers()]) + self.lexers = [] + removed = [] + for cat, ls in settings.categories: + for l in ls: + removed.append(l) + self.lexers.append((cat, [(x, lexers[x]) for x in ls])) + for l in removed: + try: + del lexers[l] + except KeyError: + pass + self.lexers.append(('Others' if settings.categories else 'Syntax', sorted(lexers.items(), cmp = lambda a, b: cmp(a[0], b[0])))) + self.formatter = HtmlFormatter(linenos = 'table', lineanchors = 'line', anchorlinenos = True) + + def paste(self): + if self.environ['REQUEST_METHOD'] == 'POST': + mp = cgi.FieldStorage(fp = self.environ['wsgi.input'], environ = self.environ, keep_blank_values = True) + if mp['type'].value == 'Preview': + return self.preview(mp) + elif mp['type'].value == 'Paste': + return self.add_paste(mp) + else: + self.start_response('400 Bad Request', [('Content-Type', 'text/plain')]) + return ['Invalid type "%s".' % mp['type'].value] + self.start_response('200 OK', [('Content-Type', 'text/html')]) + return [str(templates.paste(searchList = { + 'title': settings.pastebin_name, + 'header': settings.pastebin_name, + 'lexers': self.lexers, + }))] + + def preview(self, mp): + lex = get_lexer_by_name(mp['syntax'].value) + text = highlight(mp['text'].value, lex, self.formatter) + self.start_response('200 OK', [('Content-Type', 'text/html')]) + return [str(templates.view(searchList = { + 'title': settings.pastebin_name, + 'header': '%s – Preview' % settings.pastebin_name, + 'hash': None, + 'date': datetime.datetime.utcnow().ctime(), + 'nick': mp['nick'].value or 'Anoynmous', + 'syntax': lex.name, + 'pastetitle': mp['title'].value or 'Untitled', + 'text': text, + }))] + + def add_paste(self, mp): + nick = mp['nick'].value + syntax = mp['syntax'].value + title = mp['title'].value + text = mp['text'].value + + hash = hashlib.md5(datetime.datetime.utcnow().ctime() + self.environ['REMOTE_ADDR']).hexdigest() + + try: + session = db.Session() + paste = db.Paste(hash, nick, datetime.datetime.utcnow(), syntax, title, text) + session.add(paste) + session.commit() + finally: + session.close() + + self.start_response('302 Found', [('Location', '/view/%s' % hash)]) + return [] + + def view(self): + hash = self.path[1] + + try: + session = db.Session() + paste = session.query(db.Paste).filter_by(hash = hash).one() + finally: + session.close() + + lex = get_lexer_by_name(paste.syntax) + text = highlight(paste.text, lex, self.formatter) + self.start_response('200 OK', [('Content-Type', 'text/html')]) + return [str(templates.view(searchList = { + 'title': '%s – View paste – %s' % (settings.pastebin_name, paste.title), + 'header': '%s – View paste' % settings.pastebin_name, + 'hash': hash, + 'date': paste.date.ctime(), + 'nick': paste.nick or 'Anonymous', + 'syntax': lex.name, + 'pastetitle': paste.title or 'Untitled', + 'text': text, + }))] + + def raw(self): + hash = self.path[1] + + try: + session = db.Session() + paste = session.query(db.Paste).filter_by(hash = hash).one() + finally: + session.close() + + self.start_response('200 OK', [('Content-Type', 'text/plain')]) + return [str(paste.text)] + + def highlight_stylesheet(self): + self.start_response('200 OK', [('Content-Type', 'text/css')]) + return [self.formatter.get_style_defs()] + + def static(self): + filename = settings.static_root + os.path.sep + self.path[1] + if not self.path[1] in ('paste.css', 'edit.js') or not os.path.exists(filename): + self.start_response('404 Not Found', [('Content-Type', 'text/html'), ('Location', '/')]) + return ['<a href="/">asdf</a>'] + self.start_response('200 OK', [('Content-Type', mimetypes.guess_type(filename)[0] or 'text/plain')]) + return open(filename) + + def __call__(self, environ, start_response): + self.environ = environ + self.start_response = start_response + + path = self.environ['PATH_INFO'].split('/')[1:] + module = path[0] or 'paste' + if module in ('list', 'paste', 'view', 'raw', 'static', 'highlight_stylesheet'): + self.path = path + return getattr(self, module)() + else: + start_response('404 Not Found', [('Content-Type', 'text/html'), ('Location', '/')]) + return ['<a href="/">/</a>'] + +if __name__ == '__main__': + from wsgiref.simple_server import make_server + httpd = make_server('', 8000, Paste()) + httpd.serve_forever() diff --git a/static/edit.js b/static/edit.js new file mode 100644 index 0000000..8e754d8 --- /dev/null +++ b/static/edit.js @@ -0,0 +1,83 @@ +function tab_add(t, ti, ss, se) { + t.value = t.value.substr(0, ti) + "\t" + t.value.substr(ti); + t.selectionStart = ss + 1; + t.selectionEnd = se + 1; +} + +function tab_remove(t, ti, ss, se) { + t.value = t.value.substr(0, ti-1) + t.value.substr(ti); + t.selectionStart = ss - 1; + t.selectionEnd = se - 1; +} + +function textarea_handle_tab(t, e) { + if(navigator.userAgent.match("Gecko")) { + keyCode = e.which; + } else { + keyCode = e.keyCode; + } + if(keyCode != 9) + return; + e.preventDefault(); + var ss = t.selectionStart; + var se = t.selectionEnd; + if(ss == se) { + if(e.shiftKey) { + if(t.value[ss-1] == "\t") { + tab_remove(t, ss, ss, se); + } + } else { + tab_add(t, ss, ss, se); + } + } else { + var s; + if(ss < se) + s = t.value.substr(ss, se - ss); + else + s = t.value.substr(se, ss - se); + if(s.indexOf("\n") > -1) { + var nl = s.lastIndexOf("\n"); + if(e.shiftKey) { + while(nl > -1) { + if(s[nl+1] == "\t") { + tab_remove(t, ss + nl + 2, ss, se); + se--; + } + nl = s.lastIndexOf("\n", nl - 2); + } + if(t.value[(ss < se ? ss : se)-1] == "\n" && e.shiftKey) + tab_remove(t, ss+1, ss, se); + if((ss == 0 || se == 0) && t.value[0] == "\t") + tab_remove(t, 1, ss, se); + } else { + while(nl > -1) { + tab_add(t, ss + nl + 1, ss, se); + se++; + if(nl == 0) + break; + nl = s.lastIndexOf("\n", nl - 2); + } + if(ss == 0 || se == 0 || t.value[(ss < se ? ss : se)-1] == "\n") + tab_add(t, ss, ss-1, se); + } + + } else { + var nl = t.value.lastIndexOf("\n", (ss < se ? se : ss) - 1) + 1; + if(e.shiftKey) { + if(t.value[nl] == "\t") { + tab_remove(t, nl + 1, ss, se); + } + } else { + tab_add(t, nl, ss, se); + } + } + } + setTimeout("document.getElementById('" + t.id + "').focus();", 0); +} + +window.onload = function() { + text = document.getElementById("text"); + text.onkeydown = function(event) { + return textarea_handle_tab(text, event); + } +} diff --git a/static/paste.css b/static/paste.css new file mode 100644 index 0000000..11e15a0 --- /dev/null +++ b/static/paste.css @@ -0,0 +1,18 @@ +html, body { margin: 0; padding: 0; font-family: sans-serif; background-color: #fff; color: #000; } +div#page { background: #eee; margin: 1em; } +div#page-header, div#page-content { padding: 1em; } +div#page-header h1 a { color: inherit; } +div#page-header h1 a:hover { text-decoration: none; } +a { color: #00f; text-decoration: none; } +a:hover { text-decoration: underline; } +form#pasteform ul { list-style-type: none; } +div#info { font-size: small; color: #bbb; } +span#nick, span#date, span#syntax-type { color: #888; } +div#alt-links { font-size: small; } +div#alt-links a { color: #44f; } +.highlighttable { width: 100%; padding-top: 1em; } +.highlighttable td.code { width: 100%; background-color: #fff; } +.highlighttable td { vertical-align: top; } +.highlighttable pre { margin: 0; padding: 0; } +.highlighttable .linenos { text-align: right; padding-left: .5em; padding-right: .5em; } +.highlighttable .linenos a { color: #888; } diff --git a/templates/__init__.py b/templates/__init__.py new file mode 100644 index 0000000..1dd71e0 --- /dev/null +++ b/templates/__init__.py @@ -0,0 +1,3 @@ +from list import list +from paste import paste +from view import view diff --git a/templates/base.tmpl b/templates/base.tmpl new file mode 100644 index 0000000..55b420e --- /dev/null +++ b/templates/base.tmpl @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>$title</title> + <link rel="StyleSheet" href="/static/paste.css" type="text/css" /> +#block head +#end block + </head> + <body> + <div id="page"> + <div id="page-header"> + <h1><a href="/">$header</a></h1> + </div> + <div id="page-content"> +#block content +#end block + </div> + </div> + </body> +</html> diff --git a/templates/paste.tmpl b/templates/paste.tmpl new file mode 100644 index 0000000..0a201fe --- /dev/null +++ b/templates/paste.tmpl @@ -0,0 +1,28 @@ +#extends templates.base +#def head + <script src="/static/edit.js" type="text/javascript"></script> +#end def +#def content + <form action="/paste" method="post" id="pasteform"> + <ul> + <li id="syntaxli"> + <select name="syntax" id="syntax"> +#for cat, ls in $lexers + <optgroup label="$cat"> +#for k, v in $ls + <option value="$v">$k</option> +#end for + </optgroup> +#end for + </select> + </li> + <li><input type="text" name="nick" id="nick" value="Anonymous" /></li> + <li><input type="text" name="title" id="title" value="Untitled" /></li> + <li><textarea rows="15" cols="80" name="text" id="text"></textarea></li> + <li> + <input type="submit" id="pastesubmit" name="type" value="Paste" /> + <input type="submit" id="pastesubmit2" name="type" value="Preview" /> + </li> + </ul> + </form> +#end def diff --git a/templates/view.tmpl b/templates/view.tmpl new file mode 100644 index 0000000..898a08e --- /dev/null +++ b/templates/view.tmpl @@ -0,0 +1,22 @@ +#extends templates.base +#def head + <link rel="StyleSheet" href="/highlight_stylesheet" type="text/css" /> +#end def +#def content + <p><a href="/">New paste</a></p> + <div id="paste"> + <h3>$pastetitle</h3> + <div id="info">Pasted by <span id="nick">$nick</span> on <span id="date">$date</span>#slurp +#if $syntax + as <span id="syntax-type">$syntax</span>#slurp +#end if +. +</div> + <div id="alt-links"> +#if $hash + <a href="/raw/$hash">Download as plain text</a> +#end if + </div> + $text + </div> +#end def |