Commits

Anonymous committed d3e3967

- working index
- copy & diff of snippet implemented
- css fixed

  • Participants
  • Parent commits c47491d

Comments (0)

Files changed (12)

File friendpaste/__init__.py

 import web
 import sys
-import Queue
 import config
-from snippets.views import Snippet, Copy
-from core.indexer import IndexWorker
+from snippets.views import Snippet, Copy, Download, List, Diff, Rawdiff
+import core.indexer
 
-paste_queue = Queue.Queue()
 
 render = web.template.render(config.TEMPLATES_DIR)
 web.template.Template.globals['DEBUG'] = config.DEBUG
 
 urls = (  
         '/(.*)/', 'item',
+        '/list', 'List',
         '/(?P<snippetid>\w*)', 'Snippet',
-        '/(?P<snippetid>\w+)/copy', 'Copy',   
+        '/(?P<snippetid>\w+)/copy', 'Copy',  
+        '/(?P<snippetid>\w+)/raw', 'Download',
+        '/(?P<snippetid>\w+)/diff/(?P<parentid>\w+)', 'Diff',
+        '/(?P<snippetid>\w+)/diff/(?P<parentid>\w+)/raw', 'Rawdiff'
 )
 
 def delegate(path):
     if not path.endswith('/'):
         web.seeother("/" + path)
 
-
-def testing():
-    pass
-
-idx = IndexWorker(paste_queue)
-
-
 class item:
     GET = POST = lambda self, path: delegate(path)
 
+
 _actions = []
 def action(f):
     """Decorator to register an friendpaste action."""
 
 
 @action
+def shell():
+    """Interactive Shell"""
+    from code import InteractiveConsole
+    console = InteractiveConsole()
+    console.push("import friendpaste")
+    console.interact()
+
+
+@action
 def startserver():
-    idx.start()
     web.run(urls, globals(), *config.middleware)
 
 

File friendpaste/core/indexer.py

 # -*- coding: utf-8 -*-
 import friendpaste
 
+import web
+import struct
 import threading
-from datetime import date
+import Queue
+import os
 
-import struct
 _pack = struct.pack
 _unpack = struct.unpack
+_path = os.path
 
+class IndexNotfound(Exception):
+    pass
 
+indexformat = ">i8s4x"
 
-nullid = "\0" * 20
-indexformatv0 = "i20s"
+class indexio(object):
 
-class logio(object):
     def __init__(self):
-        self.size = struct.calcsize(indexformatv0)
+        self.size = struct.calcsize(indexformat)
 
-    def packentry(self, timestamp, snippetid):
-        e = (timestamp, snippetid)
-        return _pack(indexformatv0, *e)
+    def read(self, start, limit):
+        ifn = "%s/00changelog.i" % friendpaste.config.DATA_PATH
+        if not _path.exists(ifn):
+            raise IndexNotfound
 
+        fsize = os.stat(ifn).st_size
+        size= limit * self.size
+
+        nmax = fsize / self.size
+        if start>=nmax:
+            return 0,0
+        
+        cur = fsize - start * self.size
+        offset = cur - limit * self.size
+        if offset < 0:
+            offset = 0
+
+       
+        nb_records = (cur - offset) / self.size
+        ifh = open(ifn, 'r')
+        ifh.seek(start)
+        results = []
+        for i in xrange(nb_records):
+            l = ifh.read(self.size)
+            e = _unpack(indexformat, l)
+            results.append(e)
+        ifh.close()
+        results.reverse()
+        return results, nb_records 
+
+    def packentry(self, id, timestamp):
+        e = (timestamp, id)
+        return _pack(indexformat, *e)
 
 class IndexWorker(threading.Thread):
-    def __init__(self, queue):
+
+    def __init__(self):
         super(IndexWorker, self).__init__()
-        self.setDaemon(1)
-        
-        self.f = open('%s/changelog.i' % friendpaste.config.DATA_PATH, mode='a')  
-        self.q = queue
-        self._io = logio()
-        self.last=[]
+        self.setDaemon(True)
+        self.q = Queue.Queue()
+        self._io = indexio()
+        ifn = "%s/00changelog.i" % friendpaste.config.DATA_PATH
+        self.ifh = open(ifn, "a+")
+        self.start()
 
-    def index(self, snippet):
 
+    def put(self, snippet):
+        self.q.put((snippet.id, snippet.created))
 
-        entry = self._io.packentry(snippet.created, snippet.id)
-        self.f.write(entry)
-        self.f.flush()
-        self.last.append(snippet)
-        self.q.task_done()
+    def _save(self, snippetid, timestamp):
+        e = self._io.packentry(snippetid, timestamp)
+        self.ifh.write(e)
+        self.ifh.flush()
 
     def run(self):
         while True:
-            snippet = self.q.get() 
-            self.index(snippet)
-        self.f.close()
-            
+            id, timestamp = self.q.get()
+            self._save(id, timestamp)
 
 
+def _load_index():
+    web.webapi.ctx.index = IndexWorker()
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+web.webapi.loadhooks['index']=_load_index

File friendpaste/snippets/code.py

 class Snippet(object):
     def __init__(self):
         self.id = None
-        self.title = ""
-        self.language = ""
-        self.content = ""
+        self.title = None
+        self.language = None
+        self.code = None
         self.raw_content = None
 
     def _parse(self):
                 for t in MANDATORY_TAGS:
                     if t not in tags_seen:
                         raise MetadataException("Missing tag '%s' in snippet metadata" % (t))
-                setattr(self, 'content', io_obj.read())
+                self.code = io_obj.read()
                 self._highlight()
                 end_reached = True
             elif METADATA_COMMENT_RE.match(line):
 
     def _highlight(self):
         lexer = lexers.get_lexer_by_name(self.language)
-        highlighted = highlight(self.content, lexer,
-                formatters.HtmlFormatter(linenos='inline', 
+        highlighted = highlight(self.code, lexer,
+                formatters.HtmlFormatter(linenos=True, 
                     cssclass="source", lineseparator="<br />"))
         setattr(self, 'highlighted', highlighted)
 
                 self.id = id
                 self.filename = fn
                 found = True
-                print path
             elif not os.path.exists(fn):
                 self.id = id
                 self.filename = fn
         f = open(self.filename, 'w')
         f.write(self.raw_content)
         f.close()
-        self.created = os.stat(self.filename).st_mtime
-
+        setattr(self, 'created', os.stat(self.filename).st_mtime)
 
     def get (self, id):
         if len(id) <= 4:
-            raise
+            raise ValueError, "id invalid"
 
         dirs = '/'.join(c for c in id[0:4])
         fn = "%s/%s/%s" % (friendpaste.config.DATA_PATH,dirs, id[4:])
         if not os.path.exists(fn):
-            raise
+            raise LookupError, "paste don't exist"
        
         self.id = id
         self.filename = fn
         self.raw_content = f.read()
         f.close()
         self._parse()
-        self.created = os.stat(self.filename).st_mtime
+        setattr(self, 'created', os.stat(self.filename).st_mtime)
 
     def __unicode__(self):
         return u"%s" % self.content
     s = Snippet()
     s.save(snippet)
 
-    friendpaste.paste_queue.put(s)
+    idx=web.ctx.index
+    idx.put(s)
     return s
 
 
     try:
         s.get(id)
     except:
-        raise
+        raise LookupError, "paste don't exist"
     return s
-

File friendpaste/snippets/views.py

 # -*- coding: utf-8 -*-
+import friendpaste
+from friendpaste.snippets import LEXERS_CHOICE, main_lexers
+
 import web
 from web.request import autodelegate
 from web import form
-
-import friendpaste
-
-from friendpaste.snippets import LEXERS_CHOICE, main_lexers
+from datetime import datetime
 
 from code import save_snippet, get_snippet
 
 snippet_form = form.Form(
         form.Textbox("title"),
-        form.Textarea("snippet", id="snippet", css="{required:true}"),
+        form.Textarea("snippet", id="snippet", class_="{required:true}", rows="8", cols="10"),
         form.Dropdown("language", LEXERS_CHOICE, value='text'),
         form.Hidden("parent"),
         )
                 snippet = get_snippet(snippetid)
             except:
                 return web.notfound()
-            form['snippet'].value = snippet.content
+            form['snippet'].value = snippet.code
             form['language'].value = snippet.language
             form['parent'].value = snippetid
-            print friendpaste.render.base(friendpaste.render.snippet_details(snippet,
-                form))
+            snippet.created = datetime.fromtimestamp(snippet.created)
+
+            print friendpaste.render.base(friendpaste.render.snippet_details(snippet, form))
         else:
-            print friendpaste.render.base(friendpaste.render.snippet_form(form,
-                None))
+            print friendpaste.render.base(friendpaste.render.snippet_form(form, None))
 
     def POST(self, snippetid):
-        
         form = snippet_form()
         if not form.validates(): 
             if form['parent'].value:
                     parent = None
             else:
                 parent=None
-            print friendpaste.render.base(friendpaste.render.snippet_form(form,
-                parent))
+            print friendpaste.render.base(friendpaste.render.snippet_form(form, parent))
         else:
             snippet = save_snippet(form)
             web.redirect("/%s" % snippet.id)
             return web.notfound()
 
         form = snippet_form()
-        form['snippet'].value = snippet.content
+        form['snippet'].value = snippet.code
         form['language'].value = snippet.language
         form['parent'].value = snippetid
-        print friendpaste.render.base(friendpaste.render.snippet_form(form,
-            snippet))
+        print friendpaste.render.base(friendpaste.render.snippet_form(form,snippet))
+
+
+class Download:
+    def GET(self, snippetid):
+        try:
+            snippet = get_snippet(snippetid)
+        except:
+            return web.notfound()
+       
+        web.header('content-type', 'text/plain') 
+        print snippet.code
+ 
+class List:
+    def GET(self):
+        from friendpaste.core.indexer import indexio
+        _io = indexio()
+
+        rst, nb_snippets = _io.read(0, 30)
+        web.header('content-type', 'text/html')
+        print "<ul>"
+        for r in rst:
+            s = get_snippet(r[1])
+            print '<li><a href="/%s">%s</a></li>' % (s.id, s.id)
+        print "</ul>"
+
+
+def diff_str(a, b):
+    from difflib import unified_diff
+    diff = unified_diff(a,b)
+    s =''
+    for l in diff:
+            s+=l
+    return s
+
+class Diff:
+    def GET(self, snippetid, parentid):
+        snippet = None
+        psnippet = None
+        try:
+            snippet = get_snippet(snippetid)
+        except:
+            return web.notfound()
+        cur = snippet.code.split('\n')
+
+        try:
+            psnippet = get_snippet(parentid)
+        except:
+            orig=''
+        if psnippet:
+            orig = psnippet.code.split('\n')
+        
+        diff = diff_str(orig, cur)
+
+        from pygments import highlight, lexers, formatters
+        lexer = lexers.get_lexer_by_name('diff')
+        diff = highlight(diff, lexer,
+                formatters.HtmlFormatter(linenos=True, 
+                    cssclass="source", lineseparator="<br />"))
+        print friendpaste.render.base(friendpaste.render.snippet_diff(diff,snippet, psnippet))
+
+class Rawdiff:
+     def GET(self, snippetid, parentid):
+        snippet = None
+        psnippet = None
+        try:
+            snippet = get_snippet(snippetid)
+        except:
+            return web.notfound()
+        cur = snippet.code.split('\n')
+
+        try:
+            psnippet = get_snippet(parentid)
+        except:
+            orig=''
+        if psnippet:
+            orig = psnippet.code.split('\n')
+        diff = diff_str(orig,cur)
+
+        web.header('content-type', 'text/plain')
+        print diff

File index/20071218.i

Empty file removed.

File static/css/base.css

 body { 
 	margin: 0;
 	padding: 0;
-	background: #f5f8fa;
+	background: #f5f8fa url(../images/bg.gif) repeat-x;
 	font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
 	font-size: 12px;
 	color: #000;
-	}
+}
 ul, ol, li, h1, h2, h3, h4, h5, p { margin: 0 0 0.5em 0; padding: 0; }
 strong { font-weight: bold;}
-a:link { text-decoration: none; color: #000; }
-a:visited { text-decoration: none; color: #000; }
+a:link { text-decoration: none; color: #fe4e00; }
+a:visited { text-decoration: none; color: #fe4e00; }
 a:hover { text-decoration: underline; }
 a:active { text-decoration: underline; }
 
 h2 .info { font-size: 0.9em; }
 
-.info a { color: #00344d; }
-.info a:hover { color: #000; }
+
+
+.content { padding-left: 50px; }
+
+#header { height: 40px; background: #d9e5eb; padding-left: 50px;}
+#footer { width: 100%; padding: 10px 0 0 50px; }
+
+/*.container { margin: 0; }*/
+
+.newpaste { font-weight: bold; font-size: 1.2em; }
+
 
 /* form */
 input,
 	padding: 2px 5px;
 	height: 25px;
 	border: 1px solid #fe4e00;
-	background: url(../images/boutonAbg-2s.gif) top repeat-x #fe4e00;
+	background: #fe4e00 url(../images/boutonAbg-2s.gif) top repeat-x;
 	font-size: 12px;
 	color: #fff;
 	}
 input[type="button"]:hover,
 input[type="submit"]:hover,
 input[type="reset"]:hover {
-	background: url(../images/boutonAbg-2s.gif) bottom repeat-x transparent #fe8900;
+	background: #fe8900 url(../images/boutonAbg-2s.gif) bottom repeat-x transparent;
 	cursor: pointer;
 	color: #000;
 	}
 .aligned label.inline { display:inline; float:none; }
 .submit-row { padding-top:10px; line-height: 1.3em; vertical-align: middle; }
 
-#snippet-detail { width: 98%; }
-#snippet { width: 98%; height: 250px; }
+#snippet-edit, #snippet-detail, #snippet-diff { position: relative; width: 95%; }
+#snippet { width: 95%; height: 250px; }

File static/css/highlight.css

 
 
 /* custom */
-span.lineno { display: block; float: left; color: #999; background: #eee; border-right: 1px solid #ccc; vertical-align: top; text-align: middle; padding-right: 5px; width: 18px; lin-height: 1.1em;}
+span.lineno { display: block; float: left; color: #999; background: #eee; border-right: 1px solid #ccc; vertical-align: top; text-align: center; padding-right: 5px; width: 18px; line-height: 1.1em;}
 .action a { text-decoration: none; }
 .action a:hover { text-decoration: underline; }
 
 .hilight pre { font-family: "Bitstream Vera Sans Mono", "Courier New", Monaco, monospace;  font-size: 1em; padding-top: 0.1em; }
 
 
-.hilight { overflow: auto; padding; 0; max-height: 35em;}
+.hilight { overflow: auto; padding: 0; max-height: 35em;}

File static/images/bg.gif

Added
New image

File templates/base.html

     <title>Friendpaste - $self.title</title>
     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 
-    <link rel="stylesheet" href="/static/css/blueprint/screen.css" type="text/css" media="screen, projection">
-    <link rel="stylesheet" href="/static/css/blueprint/print.css" type="text/css" media="print">    
+    <link rel="stylesheet" href="/static/css/blueprint/screen.css" type="text/css" media="screen, projection" />
+    <link rel="stylesheet" href="/static/css/blueprint/print.css" type="text/css" media="print" />    
     <!--[if IE]><link rel="stylesheet" href="/static/css/blueprint/lib/ie.css" type="text/css" media="screen, projection"><![endif]-->
 
-    <link rel="stylesheet" href="/static/css/base.css" type="text/css" media="screen, projection">
-    <link rel="stylesheet" href="/static/css/highlight.css" type="text/css">
+    <link rel="stylesheet" href="/static/css/base.css" type="text/css" />
+    <link rel="stylesheet" href="/static/css/highlight.css" type="text/css" />
 
 $if DEBUG:
     <script type="text/javascript" src="/static/js/jquery-1.2.1.js"></script>
     <script type="text/javascript" src="/static/js/jquery-1.2.1.min.js"></script>
 </head>
 <body>
-    <div id="container"> 
-      <div class="column span-24">
+    <div class="container"> 
+        <div class="column span-24" id="header">
                 Header
+        </div>
+ 
+
+        <div class="column span-20">
+            
+            <div class="content">
+                $:self
             </div>
-      <div class="column span-5">
-                Left sidebar
-      </div>
+        </div>
+        <div class="column span-4 last">
+            <ul>
+                <li><a href="/" class="newpaste">New paste</a></li>
+            </ul>
 
-      <div class="column span-19 content last">
-                $:self
+            $:self.sidebar
+
         </div>
-      <div class="column span-24">
-        2007 &copy; Enki Multimedia 
-      </div>
+        <div class="column span-24" id="footer">
+        2007 &copy; Enki Multimedia - Some rights reserved 
+        </div>
       
     </div>
 </body>

File templates/snippet_details.html

 $def with (snippet, form)
 
 $var title: $snippet.title
+$var sidebar:
+    <div id="paste-detail">
+        <h3>paste detail</h3>
+        <ul>
+            <li><a href="/$snippet.id/copy">reply paste</a></li>
+            <li><a href="/$snippet.id/raw">download paste</a></li>
+            $if snippet.parent:
+                <li><a href="/$snippet.id/diff/$snippet.parent">diff from parent</a></li>
+        </ul>
+    </div>
 
 <h2 class="title">$snippet.title</h2>
-<p class="info">Double-click on
-        snippet to edit or click <a href="/$snippet.id/copy"
-            class="copy">here</a></p>
-<div id="snippet-detail" class="hilight edit">
-    $:snippet.highlighted
+<p class="info">Double-click on snippet to edit or click <a href="/$snippet.id/copy"class="copy">here</a></p>
+<div id="snippet-detail">
+    <p>Link to this snippet : <a href="/$snippet.id">htpp://friendpaste.com/$snippet.id</a>&nbsp;(created at $snippet.created.strftime("%Y-%m-%d %H:%M:%S"))</p>
+        
+        <div class="hilight">
+            $:snippet.highlighted
+        </div>
 </div>
 
-
 <form id="snippet-edit" name="main" method="post" action="/" class="aligned">
     $:form['parent'].render()
     $if not form.valid: <p class="error">Try again :</p>
     <div class="form-row">$:form['snippet'].render()</div>
     <div class="form-row"><label for="language">Syntax hilighting</label> $:form['language'].render()</div>
     <div class="form-row"><label for="title">Title/ Name</label> $:form['title'].render()</div>
-    <div class="submit-row"><input type="submit"value="Submit post"
-        />&nbsp;<input type="reset" value="Reset Form"></div>
+    <div class="submit-row"><input type="submit" value="Submit post" />&nbsp;<input type="reset" value="Reset Form" /> <input type="button" id="cancel" value="Cancel" /></div>
 </form>
 
 <script type="text/javascript">
                 'hide' }, 'fast');
             \$("#snippet-edit").animate({ height: 'show', opacity:
                 'show' }, 'fast');
+            });
+
+    $("#cancel").click(function(event) {
+        \$(".title").html("$snippet.title")
+            \$(".info").show()
+            \$("#snippet-edit").animate({ height: 'hide', opacity:
+                'hide' }, 'fast');
+            \$("#snippet-detail").animate({ height: 'show', opacity:
+                'show' }, 'fast');
     });
 </script>

File templates/snippet_diff.html

+$def with (diff, snippet, parent)
+
+$var title: Diff from $parent.id to $snippet.id
+$var sidebar:
+    <div id="paste-detail">
+        <ul>
+            <li><a href="/$snippet.id">back to paste</a></li>
+            <li><a href="/$parent.id">go to parent</a></li>
+            <li><a href="/$snippet.id/diff/$parent.id/raw">download diff</a></li>
+        </ul>
+    </div>
+
+
+<div id="snippet-diff">
+    <h2>Diff from <a href="/$parent.id">$parent.id</a> to <a href="/$snippet.id">$snippet.id</a></h2>
+
+    <div class="hilight">
+        $:diff
+    </div>
+</div>

File templates/snippet_form.html

 $def with (form, parent)
 
 $var title: "Copy/ Paste and share over Internet"
+$var sidebar:
 
-<form name="main" method="post" action="/" class="aligned">
+<form id="snippet-edit" name="main" method="post" action="/" class="aligned">
     $:form['parent'].render()
     $if not form.valid: <p class="error">Try again :</p>
     
     <div class="form-row">$:form['snippet'].render()</div>
     <div class="form-row"><label for="language">Syntax hilighting</label> $:form['language'].render()</div>
     <div class="form-row"><label for="title">Title/ Name</label> $:form['title'].render()</div>
-    <div class="submit-row"><input type="submit"value="Submit post"
-        />&nbsp;<input type="reset" value="Reset Form"></div>
+    <div class="submit-row"><input type="submit" value="Submit post" />&nbsp;<input type="reset" value="Reset Form" /></div>
 </form>