#!/usr/bin/python
"""
noets - A weblog-style site CGI.
License: GPL 2; share and enjoy!
Author: Sean B. Palmer, inamidst.com
Modifications: Christopher Schmidt
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.
Modifications:
RSS 1.1 output and /about support was added by Christopher
Schmidt, 01-2005. Modified posting to require a % before
each line, for more concentrated effort required to post,
with the benefit that you don't accidentally post anything.
Added additional metadata display to /archives view.
"""
import sys, os, re, cgi, time
from cStringIO import StringIO
base = '/noets'
bloguri = 'http://crschmidt.net/noets/'
blogname = 'nœts'
password = 'password here'
navigation = '
\n'
navigation += '
\n'
navigation += '
Delivering news from the mists of Freenode since '
navigation += '2005: [home]
\n'
address = '\n'
address += 'Christopher Schmidt, \n'
address += 'and other various and sundry characters.\n'
address += '\n'
address += '
All code which runs this thing was stolen from sbp. Based on the original nœts site, circa 2003. Design by d8uv, xover, with help from the works of Eric Meyer. Any other people who were involved are also credited and thanked here, in this very small space: . Thank you.
'
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 '%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'%base
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())
tags = ""
try:
tags = form.get(tags)
except:
pass
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')
if tags: print >> meta, 'tags:', tags
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'))
tags = meta.get('tags') or ''
entry = '
\n'
return title, entry
def buildEntry(i):
title, ent = getEntry(i)
entry = navigation + ent + address
return html('%s - %s' % (blogname, title), entry)
def buildArchives(nickname):
filenames = getEntryIDs()
filenames.reverse()
result = navigation
result += '
\n
\n'
months = {}
for filename in filenames:
meta = dict(r_meta.findall(open('./%s.txt' % filename, 'r').read()))
date, title = meta.get('datetime') or '', meta.get('title')
date = date[:10]
month = date[:7]
nick = meta.get('nick')
if (nickname == '' or nickname == nick):
channel = meta.get('channel') or ''
if not months.has_key(month):
result += '
\n'
result += address
return html('%s - archives' % blogname, result)
def buildMonthArchive(month):
filenames = getEntryIDs()
r_month = re.compile('(?m)^datetime: %s' % month)
result = navigation
result += '
%s Archive
' % month
inSeq = False
for filename in filenames:
if r_month.search(open('./%s.txt' % filename, 'r').read()):
inSeq = True
title, entry = getEntry(filename)
result += '
\n' + entry + '
\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
rss += '%s\n' % blogname
rss += '%s\n' % bloguri
rss += 'by crschmidt and friends\n'
rss += 'en\n'
rss += 'Sean B. Palmer\n'
t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
rss += '%s\n' % t
filenames = getEntryIDs()[-n:] # get the last ten or so entries
filenames.reverse()
rss += '\n'
for filename in filenames:
meta = dict(r_meta.findall(open('./%s.txt' % filename, 'r').read()))
data = open('./%s.html' % filename, 'r').read()
ordata = data
data = data.replace(']]>', ']]>]]>\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 += '\t%s\n' % ordata
rss += '\n'
rss += '\n'
rss += '\n'
return rss
def buildRSS10(n=None):
if n is None:
n = 10
rss = '\n'
rss += '\n' % bloguri
rss += '%s\n' % blogname
rss += '%s\n' % bloguri
rss += 'by crschmidt and friends\n'
rss += 'en\n'
rss += 'Sean B. Palmer\n'
t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
rss += '%s\n' % t
rss += '%s\n' % t
filenames = getEntryIDs()[-n:] # get the last ten or so entries
filenames.reverse()
rss += '\n\n'
for filename in filenames:
rss += '' % (bloguri, filename)
rss += '\n\n'
rss += '\n'
for filename in filenames:
meta = dict(r_meta.findall(open('./%s.txt' % filename, 'r').read()))
data = open('./%s.html' % filename, 'r').read()
data = data.replace(']]>', ']]>]]>\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 buildAbout():
result = navigation
result += '
%s \n' % blogname
result += '(by crschmidt & friends)
\n'
result += """
Just notes. Really.
This is a place to collect my thoughts before moving them to other places. It's also a dumping ground for people in a variety of other channels. All content is posted via an IRC interface, in several different channels.
"""
result += address
return html("%s - the notes of various channels" % blogname, result)
def buildIndex(n=None):
if n is None:
n = 10
result = navigation
result += '
%s \n' % blogname
result += '(by crschmidt & 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 notes of various channels" % 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('/var/www/crschmidt.net/htdocs/noets/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 True:
if (method == 'GET') and (path == '/0'):
print "Content-Type: text/plain"
print
print re.sub("(?m)^password = '.+'$",
"password = 'password here'",
open('index.cgi').read())
return
if (method == 'GET') and (path == '/rss'):
print "Content-Type: application/rss+xml"
print
print buildRSS()
return
if (method == 'GET') and (path == '/rss10'):
print "Content-Type: text/xml"
print
print buildRSS10()
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 == '/about'):
print buildAbout()
elif (method == 'GET') and (path == '/archives'):
print buildArchives('')
elif (method == 'GET') and ('/author' in path):
try:
print buildArchives(re.search("/author/(.*)", path).group(1))
except:
print buildArchives('')
elif (method == 'GET') and ('-' in path):
print buildMonthArchive(path[1:])
elif method == 'GET':
try:
i = int(path[1:])
print buildEntry(i)
except:
print buildIndex()
if __name__=="__main__":
try: main()
except: cgi.print_exception()