summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bergli Heier <snakebite@jvnv.net>2010-01-25 01:00:18 +0100
committerJon Bergli Heier <snakebite@jvnv.net>2010-01-25 01:00:18 +0100
commit346b22ddf795f9a5a10ddc6b83bfa3bc15c8d30a (patch)
treef9d9251ea08bc8ad57e7234ca451bcc3eeccc7b5
Initial commit.
-rw-r--r--.gitignore5
-rw-r--r--db.py32
-rw-r--r--pastepy.py137
-rw-r--r--static/edit.js83
-rw-r--r--static/paste.css18
-rw-r--r--templates/__init__.py3
-rw-r--r--templates/base.tmpl22
-rw-r--r--templates/paste.tmpl28
-rw-r--r--templates/view.tmpl22
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
diff --git a/db.py b/db.py
new file mode 100644
index 0000000..06b2a14
--- /dev/null
+++ b/db.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 &ndash; 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 &ndash; View paste &ndash; %s' % (settings.pastebin_name, paste.title),
+ 'header': '%s &ndash; 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