#!/usr/bin/python """ noets - A weblog-style site CGI. License: GPL 2; share and enjoy! Author: Sean B. Palmer, inamidst.com Date: 2003-10ish About: This script originally powered the noets website, which was written to from IRC on various different channels by any of its participants. In that respect, it was like Edd Dumbill's chump bot, but the input was oriented towards prose rather than line-by-line input, and so the output was more like a conventional weblog. If you want to use this yourself, you'll have to change all of the hardcoded site names, paths, &c. """ import sys, os, re, cgi, time from cStringIO import StringIO base = '/noets' bloguri = 'http://sbp.f2o.org/noets/' blogname = 'nœts' password = '@@ Your Password Here' navigation = '\n' address = '
\n' address += 'Sean B. Palmer, \n' address += 'and the irc.freenode.net/#sbp folk.\n' address += '
\n' def quoteHTML(s): s = s.replace('&', '&') s = s.replace('<', '<') return s r_wlink = re.compile(r'(?]+)>(?"]+$') def wikiLinkify(s): def htmlify(m): title, uri = m.group(1), m.group(2) return '%s' % (uri, title) s = r_wlink.sub(htmlify, s) return s def unicodeify(s): i = int(s, 16) if i in (0x9, 0xA, 0xD) + tuple(xrange(0x20, 0x7E)): return chr(i) elif i > 0x10FFFF: raise "UnicodeError", "Codepoint exceeds U+10FFFF" return '&#x%s;' % s def wikiFormat(s): # @@ reserve \[A-Za-z]+{...} for future extensions # @@ use a proper parser, or catch the matches using a function result = '' pos = 0 while pos < len(s): m = r_wlink.match(s[pos:]) if m: span = m.span() result += wikiLinkify(s[pos:pos+span[1]]) pos += len(m.group(0)) else: m = re.compile(r'[A-Za-z0-9]--[A-Za-z0-9]').match(s[pos:]) if m: result += s[pos] + '—' + s[pos+3] pos += 4 else: m = r_uniquot.match(s[pos:]) if m: result += unicodeify(m.group(1)) pos += len(m.group(0)) elif s[pos:pos+4] == '\n..\n': result += '

\n\n

' pos += 4 elif s[pos] == '&': result += '&' pos += 1 elif s[pos] == '<': result += '<' pos += 1 else: result += s[pos] pos += 1 return result def html(title, body): s = '\n' s += '\n' s += '%s\n' % title s += '\n' s += '\n' s += '\n' s += body s += '\n' s += '' return s def getEntryIDs(): # Each entry's content will be stored as (digit).html # So we can use that as a kind of entry-index filenames = filter(lambda s: (s.endswith('.html') and s[:-len('.html')].isdigit), os.listdir('.')) filenames = [int(fn[:-len('.html')]) for fn in filenames] filenames.sort() return filenames def postEntry(): form = sys.stdin.read() form = cgi.parse_qs(form) form = dict([(item[0], ''.join(item[1])) for item in form.items()]) if form.get('pass') == password: # Compute the next available entry ID filenames = getEntryIDs() if filenames: nextid = filenames[-1] + 1 else: nextid = 1 # Metadata t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) meta = open('./%s.txt' % nextid, 'w') print >> meta, 'title:', form.get('title') print >> meta, 'datetime:', t print >> meta, 'nick:', form.get('nick') print >> meta, 'channel:', form.get('channel') meta.close() # Data content = str(form.get('content')) data = open('./%s.html' % nextid, 'w') print >> data, '

%s

' % wikiFormat(content) data.close() # Return the ID as a string return str(nextid) r_meta = re.compile('(?s)([^\n:]+): (.*?)(?=\n[^ \t]|\Z)') def getEntry(i): meta = dict(r_meta.findall(open('./%s.txt' % i, 'r').read())) data = open('./%s.html' % i, 'r').read() title = quoteHTML(meta.get('title')) datetime = meta.get('datetime') datetime = datetime.replace('T', ' ').replace('Z', ' UTC') nick = quoteHTML(meta.get('nick')) channel = quoteHTML(meta.get('channel')) entry = '
\n' entry += '

%s

\n' % title entry += data + '\n' entry += '
* Posted by %s on %s at \n\n' elif inSeq: break result += address return html('%s - archives for %s' % (blogname, month), result) def buildRSS(n=None): if n is None: n = 10 rss = '', ']]>]]>\n' % (bloguri, filename) rss += '\t%s\n' % quoteHTML(meta.get('title')) rss += '\t%s%s\n' % (bloguri, filename) rss += '\t\n' % data rss += '\t%s\n' % meta.get('nick') rss += '\t%s' % meta.get('datetime') rss += '\t\n' % data rss += '\n' rss += '\n' return rss def buildIndex(n=None): if n is None: n = 10 result = navigation result += '

%s \n' % blogname result += '(by sbp & friends)

\n' filenames = getEntryIDs()[-n:] # get the last ten or so entries if filenames: filenames.reverse() for fn in filenames: title, entry = getEntry(fn) result += '
\n' + entry + '
\n\n' else: result += '

No entries yet.

\n' result += address return html("%s - the noets of #sbp &c." % blogname, result) def main(env=None): if env is None: env = os.environ method = env.get('REQUEST_METHOD') if method not in ('GET', 'POST'): raise "UnsupportedMethodError", "Unsupported method: %s" % method uri = env.get('REQUEST_URI') assert uri.startswith(base) # @@ path = uri[len(base):] # Log the request t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) f = open('/home/sites/sbp/misc/noets-%s.log' % t[:7], 'a') addr = env.get('REMOTE_ADDR') referer = env.get('HTTP_REFERER', '[direct]').replace(' ', '') print >> f, t, method, path, addr, referer f.close() if False: if (method == 'GET') and (path == '/0'): print "Content-Type: text/plain" print print re.sub("(?m)^password = '.+'$", "password = 'yourpasswordhere'", open('index.cgi').read()) return if (method == 'GET') and (path == '/rss'): print "Content-Type: text/xml" print print buildRSS() return print "Content-Type: text/html; charset=utf-8" print if (method == 'POST') and (path == '/'): print postEntry() elif (method == 'GET') and (path == '/'): print buildIndex() elif (method == 'GET') and (path == '/archives'): print buildArchives() elif (method == 'GET') and ('-' in path): print buildMonthArchive(path[1:]) elif method == 'GET': i = int(path[1:]) print buildEntry(i) if __name__=="__main__": try: main() except: cgi.print_exception()