#!/usr/bin/env python """ jot.py - for scribbling notes Licence: GPL 2; share and enjoy! Author: Sean B. Palmer, inamidst.com Example: http://inamidst.com/notes/jot Source: http://inamidst.com/inside/notes/jot """ import cgitb; cgitb.enable() import sys, os, re, cgi, time, marshal jottings = './jottings.txt' jotmarsh = './jotmarsh.msh' method = os.environ.get('REQUEST_METHOD') qstring = os.environ.get('QUERY_STRING') r_word = re.compile(r"[A-Za-z'-]+") form = cgi.FieldStorage() form.__call__ = lambda s: form[s].value def serve(status, body, mime=None): mime = mime or 'text/html' sys.stdout.write("Status: %s\r\n" % status) sys.stdout.write("Content-Type: %s; charset=utf-8\r\n\r\n" % mime) sys.stdout.write(body) sys.exit() def timestamp(): """Return the time, UTC, in W3CDTF format.""" return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) def getFrequencies(s): """Get the frequencies of word occurances in s.""" words = r_word.findall(s) result = {} for word in words: if result.has_key(word): result[word] += 1 else: result[word] = 1 return result def update(p, q): """Update the frequency mappings in p with those in q.""" for key in q.keys(): if p.has_key(key): p[key] += q[key] else: p[key] = q[key] return p # Create the files if they don't exist yet for filename in (jottings, jotmarsh): if not os.path.isfile(filename): try: f = open(filename, 'wb') f.write('') f.close() except IOError, e: serve(500, "Unable to create %s: %s" % (filename, e)) # Notes format will be: "W3CDTF note". # No tabs because tabs suck: one character, # arbitrarily multiple whitespace? pfft class Jottings(object): def __init__(self, fn=None, msh=None): self.fn = fn or jottings self.msh = msh or jotmarsh # Marshaled index data self.notes = {} # The actual notes, mapped from timestamp to value self.keys = [] # All the timestamps, sorted self.marsh = {} # The marshaled frequency maps self.read() def __len__(self): return len(self.keys) def read(self, fn=None, msh=None): """Read a notes file in, parsing the notes.""" fn = fn or self.fn msh = msh or self.msh f = open(fn, 'r') while True: line = f.readline() if not line: break line = line.rstrip('\r\n') i = line.find(' ') key, value = line[:i], line[i+1:] self.notes[key] = value f.close() self.keys = self.notes.keys() self.keys.sort() self.keys.reverse() f = open(msh, 'rb') try: self.marsh = marshal.load(f) except EOFError: self.marsh = {} f.close() def index(self, t): return len(self) - self.keys.index(t) def get(self, n=None): """Format the last n notes as HTML and return.""" if n is None: n = 15 notes = self.keys[:n] result = '\n' # result += str(self.marsh) return result def note(self, t): index = self.index(t) note = cgi.escape(self.notes[t]) result = '

#%s) %s (%s)

\n' % (index, note, t) result += '

' if index < len(self): p = self.keys[-(index + 1)] result += '< prev | ' % p result += 'home' if index > 1: n = self.keys[-(index - 1)] result += ' | next >' % n result += '

\n' return result def search(self, s): result = '' for key in self.keys: note = self.notes[key] if note.count(s): index = self.index(key) note = cgi.escape(note) result += '
#%s) %s (%s)
\n' % (index, note, key) return result or '

No results found.

\n' if not (method == 'POST'): notes = Jottings() else: notes = None def post(): text = form('text') # Update the marshaled mappings f = open(jotmarsh, 'rb') try: marsh = marshal.load(f) except EOFError: marsh = {} f.close() f = open(jotmarsh, 'wb') marshal.dump(update(marsh, getFrequencies(text)), f) f.close() # Append the note text, with timestamp, to the jottings file t = timestamp() f = open(jottings, 'a') print >> f, t, text f.close() msg = '

Written %s bytes. Home.

\n' serve(200, msg % len(text)) def homepage(): html = 'Jottings\n' html += """

Note:

""" if len(notes) < 1: html += '

There are no notes yet.

\n' else: html += notes.get(15) html += """

Also: source.

Sean B. Palmer
""" serve(200, html) def doStuff(): if form.has_key('t'): serve(200, notes.note(form('t'))) elif form.has_key('s'): serve(200, notes.search(form('s'))) else: serve(500, "

Fuxd.

\n") def main(): if method == 'POST': post() elif (method == 'GET') and (not qstring): homepage() elif method == 'GET': doStuff() else: serve(501, "

Method must be either GET or POST

\n") if __name__=="__main__": main()