mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			833 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			833 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Generic FAQ Wizard.
 | 
						|
 | 
						|
This is a CGI program that maintains a user-editable FAQ.  It uses RCS
 | 
						|
to keep track of changes to individual FAQ entries.  It is fully
 | 
						|
configurable; everything you might want to change when using this
 | 
						|
program to maintain some other FAQ than the Python FAQ is contained in
 | 
						|
the configuration module, faqconf.py.
 | 
						|
 | 
						|
Note that this is not an executable script; it's an importable module.
 | 
						|
The actual script to place in cgi-bin is faqw.py.
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
import sys, string, time, os, stat, regex, cgi, faqconf
 | 
						|
from faqconf import *			# This imports all uppercase names
 | 
						|
now = time.time()
 | 
						|
 | 
						|
class FileError:
 | 
						|
    def __init__(self, file):
 | 
						|
	self.file = file
 | 
						|
 | 
						|
class InvalidFile(FileError):
 | 
						|
    pass
 | 
						|
 | 
						|
class NoSuchSection(FileError):
 | 
						|
    def __init__(self, section):
 | 
						|
	FileError.__init__(self, NEWFILENAME %(section, 1))
 | 
						|
	self.section = section
 | 
						|
 | 
						|
class NoSuchFile(FileError):
 | 
						|
    def __init__(self, file, why=None):
 | 
						|
	FileError.__init__(self, file)
 | 
						|
	self.why = why
 | 
						|
 | 
						|
def replace(s, old, new):
 | 
						|
    try:
 | 
						|
	return string.replace(s, old, new)
 | 
						|
    except AttributeError:
 | 
						|
	return string.join(string.split(s, old), new)
 | 
						|
 | 
						|
def escape(s):
 | 
						|
    s = replace(s, '&', '&')
 | 
						|
    s = replace(s, '<', '<')
 | 
						|
    s = replace(s, '>', '>')
 | 
						|
    return s
 | 
						|
 | 
						|
def escapeq(s):
 | 
						|
    s = escape(s)
 | 
						|
    s = replace(s, '"', '"')
 | 
						|
    return s
 | 
						|
 | 
						|
def _interpolate(format, args, kw):
 | 
						|
    try:
 | 
						|
	quote = kw['_quote']
 | 
						|
    except KeyError:
 | 
						|
	quote = 1
 | 
						|
    d = (kw,) + args + (faqconf.__dict__,)
 | 
						|
    m = MagicDict(d, quote)
 | 
						|
    return format % m
 | 
						|
 | 
						|
def interpolate(format, *args, **kw):
 | 
						|
    return _interpolate(format, args, kw)
 | 
						|
 | 
						|
def emit(format, *args, **kw):
 | 
						|
    try:
 | 
						|
	f = kw['_file']
 | 
						|
    except KeyError:
 | 
						|
	f = sys.stdout
 | 
						|
    f.write(_interpolate(format, args, kw))
 | 
						|
 | 
						|
translate_prog = None
 | 
						|
 | 
						|
def translate(text):
 | 
						|
    global translate_prog
 | 
						|
    if not translate_prog:
 | 
						|
	url = '\(http\|ftp\|https\)://[^ \t\r\n]*'
 | 
						|
	email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
 | 
						|
	translate_prog = prog = regex.compile(url + '\|' + email)
 | 
						|
    else:
 | 
						|
	prog = translate_prog
 | 
						|
    i = 0
 | 
						|
    list = []
 | 
						|
    while 1:
 | 
						|
	j = prog.search(text, i)
 | 
						|
	if j < 0:
 | 
						|
	    break
 | 
						|
	list.append(escape(text[i:j]))
 | 
						|
	i = j
 | 
						|
	url = prog.group(0)
 | 
						|
	while url[-1] in ');:,.?\'"':
 | 
						|
	    url = url[:-1]
 | 
						|
	url = escape(url)
 | 
						|
	if ':' in url:
 | 
						|
	    repl = '<A HREF="%s">%s</A>' % (url, url)
 | 
						|
	else:
 | 
						|
	    repl = '<A HREF="mailto:%s"><%s></A>' % (url, url)
 | 
						|
	list.append(repl)
 | 
						|
	i = i + len(url)
 | 
						|
    j = len(text)
 | 
						|
    list.append(escape(text[i:j]))
 | 
						|
    return string.join(list, '')
 | 
						|
 | 
						|
emphasize_prog = None
 | 
						|
 | 
						|
def emphasize(line):
 | 
						|
    global emphasize_prog
 | 
						|
    import regsub
 | 
						|
    if not emphasize_prog:
 | 
						|
	pat = '\*\([a-zA-Z]+\)\*'
 | 
						|
	emphasize_prog = regex.compile(pat)
 | 
						|
    return regsub.gsub(emphasize_prog, '<I>\\1</I>', line)
 | 
						|
 | 
						|
revparse_prog = None
 | 
						|
 | 
						|
def revparse(rev):
 | 
						|
    global revparse_prog
 | 
						|
    if not revparse_prog:
 | 
						|
	revparse_prog = regex.compile(
 | 
						|
	    '^\([1-9][0-9]?[0-9]?\)\.\([1-9][0-9]?[0-9]?[0-9]?\)$')
 | 
						|
    if revparse_prog.match(rev) < 0:
 | 
						|
	return None
 | 
						|
    [major, minor] = map(string.atoi, revparse_prog.group(1, 2))
 | 
						|
    return major, minor
 | 
						|
 | 
						|
def load_cookies():
 | 
						|
    if not os.environ.has_key('HTTP_COOKIE'):
 | 
						|
	return {}
 | 
						|
    raw = os.environ['HTTP_COOKIE']
 | 
						|
    words = map(string.strip, string.split(raw, ';'))
 | 
						|
    cookies = {}
 | 
						|
    for word in words:
 | 
						|
	i = string.find(word, '=')
 | 
						|
	if i >= 0:
 | 
						|
	    key, value = word[:i], word[i+1:]
 | 
						|
	    cookies[key] = value
 | 
						|
    return cookies
 | 
						|
 | 
						|
def load_my_cookie():
 | 
						|
    cookies = load_cookies()
 | 
						|
    try:
 | 
						|
	value = cookies[COOKIE_NAME]
 | 
						|
    except KeyError:
 | 
						|
	return {}
 | 
						|
    import urllib
 | 
						|
    value = urllib.unquote(value)
 | 
						|
    words = string.split(value, '/')
 | 
						|
    while len(words) < 3:
 | 
						|
	words.append('')
 | 
						|
    author = string.join(words[:-2], '/')
 | 
						|
    email = words[-2]
 | 
						|
    password = words[-1]
 | 
						|
    return {'author': author,
 | 
						|
	    'email': email,
 | 
						|
	    'password': password}
 | 
						|
 | 
						|
def send_my_cookie(ui):
 | 
						|
    name = COOKIE_NAME
 | 
						|
    value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
 | 
						|
    import urllib
 | 
						|
    value = urllib.quote(value)
 | 
						|
    then = now + COOKIE_LIFETIME
 | 
						|
    gmt = time.gmtime(then)
 | 
						|
    print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
 | 
						|
    print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
 | 
						|
 | 
						|
class MagicDict:
 | 
						|
 | 
						|
    def __init__(self, d, quote):
 | 
						|
	self.__d = d
 | 
						|
	self.__quote = quote
 | 
						|
 | 
						|
    def __getitem__(self, key):
 | 
						|
	for d in self.__d:
 | 
						|
	    try:
 | 
						|
		value = d[key]
 | 
						|
		if value:
 | 
						|
		    value = str(value)
 | 
						|
		    if self.__quote:
 | 
						|
			value = escapeq(value)
 | 
						|
		    return value
 | 
						|
	    except KeyError:
 | 
						|
		pass
 | 
						|
	return ''
 | 
						|
 | 
						|
class UserInput:
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
	self.__form = cgi.FieldStorage()
 | 
						|
 | 
						|
    def __getattr__(self, name):
 | 
						|
	if name[0] == '_':
 | 
						|
	    raise AttributeError
 | 
						|
	try:
 | 
						|
	    value = self.__form[name].value
 | 
						|
	except (TypeError, KeyError):
 | 
						|
	    value = ''
 | 
						|
	else:
 | 
						|
	    value = string.strip(value)
 | 
						|
	setattr(self, name, value)
 | 
						|
	return value
 | 
						|
 | 
						|
    def __getitem__(self, key):
 | 
						|
	return getattr(self, key)
 | 
						|
 | 
						|
class FaqEntry:
 | 
						|
 | 
						|
    def __init__(self, fp, file, sec_num):
 | 
						|
	self.file = file
 | 
						|
	self.sec, self.num = sec_num
 | 
						|
	if fp:
 | 
						|
	    import rfc822
 | 
						|
	    self.__headers = rfc822.Message(fp)
 | 
						|
	    self.body = string.strip(fp.read())
 | 
						|
	else:
 | 
						|
	    self.__headers = {'title': "%d.%d. " % sec_num}
 | 
						|
	    self.body = ''
 | 
						|
 | 
						|
    def __getattr__(self, name):
 | 
						|
	if name[0] == '_':
 | 
						|
	    raise AttributeError
 | 
						|
	key = string.join(string.split(name, '_'), '-')
 | 
						|
	try:
 | 
						|
	    value = self.__headers[key]
 | 
						|
	except KeyError:
 | 
						|
	    value = ''
 | 
						|
	setattr(self, name, value)
 | 
						|
	return value
 | 
						|
 | 
						|
    def __getitem__(self, key):
 | 
						|
	return getattr(self, key)
 | 
						|
 | 
						|
    def load_version(self):
 | 
						|
	command = interpolate(SH_RLOG_H, self)
 | 
						|
	p = os.popen(command)
 | 
						|
	version = ''
 | 
						|
	while 1:
 | 
						|
	    line = p.readline()
 | 
						|
	    if not line:
 | 
						|
		break
 | 
						|
	    if line[:5] == 'head:':
 | 
						|
		version = string.strip(line[5:])
 | 
						|
	p.close()
 | 
						|
	self.version = version
 | 
						|
 | 
						|
    def getmtime(self):
 | 
						|
	if not self.last_changed_date:
 | 
						|
	    return 0
 | 
						|
	try:
 | 
						|
	    return os.stat(self.file)[stat.ST_MTIME]
 | 
						|
	except os.error:
 | 
						|
	    return 0
 | 
						|
 | 
						|
    def emit_marks(self):
 | 
						|
	mtime = self.getmtime()
 | 
						|
	if mtime >= now - DT_VERY_RECENT:
 | 
						|
	    emit(MARK_VERY_RECENT, self)
 | 
						|
	elif mtime >= now - DT_RECENT:
 | 
						|
	    emit(MARK_RECENT, self)
 | 
						|
 | 
						|
    def show(self, edit=1):
 | 
						|
	emit(ENTRY_HEADER1, self)
 | 
						|
	self.emit_marks()
 | 
						|
	emit(ENTRY_HEADER2, self)
 | 
						|
	pre = 0
 | 
						|
	raw = 0
 | 
						|
	for line in string.split(self.body, '\n'):
 | 
						|
	    # Allow the user to insert raw html into a FAQ answer
 | 
						|
	    # (Skip Montanaro, with changes by Guido)
 | 
						|
	    tag = string.lower(string.rstrip(line))
 | 
						|
	    if tag == '<html>':
 | 
						|
		raw = 1
 | 
						|
		continue
 | 
						|
	    if tag == '</html>':
 | 
						|
		raw = 0
 | 
						|
		continue
 | 
						|
	    if raw:
 | 
						|
		print line
 | 
						|
		continue
 | 
						|
	    if not string.strip(line):
 | 
						|
		if pre:
 | 
						|
		    print '</PRE>'
 | 
						|
		    pre = 0
 | 
						|
		else:
 | 
						|
		    print '<P>'
 | 
						|
	    else:
 | 
						|
		if line[0] not in string.whitespace:
 | 
						|
		    if pre:
 | 
						|
			print '</PRE>'
 | 
						|
			pre = 0
 | 
						|
		else:
 | 
						|
		    if not pre:
 | 
						|
			print '<PRE>'
 | 
						|
			pre = 1
 | 
						|
		if '/' in line or '@' in line:
 | 
						|
		    line = translate(line)
 | 
						|
		elif '<' in line or '&' in line:
 | 
						|
		    line = escape(line)
 | 
						|
 		if not pre and '*' in line:
 | 
						|
 		    line = emphasize(line)
 | 
						|
		print line
 | 
						|
	if pre:
 | 
						|
	    print '</PRE>'
 | 
						|
	    pre = 0
 | 
						|
	if edit:
 | 
						|
	    print '<P>'
 | 
						|
	    emit(ENTRY_FOOTER, self)
 | 
						|
	    if self.last_changed_date:
 | 
						|
		emit(ENTRY_LOGINFO, self)
 | 
						|
	print '<P>'
 | 
						|
 | 
						|
class FaqDir:
 | 
						|
 | 
						|
    entryclass = FaqEntry
 | 
						|
 | 
						|
    __okprog = regex.compile(OKFILENAME)
 | 
						|
 | 
						|
    def __init__(self, dir=os.curdir):
 | 
						|
	self.__dir = dir
 | 
						|
	self.__files = None
 | 
						|
 | 
						|
    def __fill(self):
 | 
						|
	if self.__files is not None:
 | 
						|
	    return
 | 
						|
	self.__files = files = []
 | 
						|
	okprog = self.__okprog
 | 
						|
	for file in os.listdir(self.__dir):
 | 
						|
	    if okprog.match(file) >= 0:
 | 
						|
		files.append(file)
 | 
						|
	files.sort()
 | 
						|
 | 
						|
    def good(self, file):
 | 
						|
	return self.__okprog.match(file) >= 0
 | 
						|
 | 
						|
    def parse(self, file):
 | 
						|
	if not self.good(file):
 | 
						|
	    return None
 | 
						|
	sec, num = self.__okprog.group(1, 2)
 | 
						|
	return string.atoi(sec), string.atoi(num)
 | 
						|
 | 
						|
    def list(self):
 | 
						|
	# XXX Caller shouldn't modify result
 | 
						|
	self.__fill()
 | 
						|
	return self.__files
 | 
						|
 | 
						|
    def open(self, file):
 | 
						|
	sec_num = self.parse(file)
 | 
						|
	if not sec_num:
 | 
						|
	    raise InvalidFile(file)
 | 
						|
	try:
 | 
						|
	    fp = open(file)
 | 
						|
	except IOError, msg:
 | 
						|
	    raise NoSuchFile(file, msg)
 | 
						|
	try:
 | 
						|
	    return self.entryclass(fp, file, sec_num)
 | 
						|
	finally:
 | 
						|
	    fp.close()
 | 
						|
 | 
						|
    def show(self, file, edit=1):
 | 
						|
	self.open(file).show(edit=edit)
 | 
						|
 | 
						|
    def new(self, section):
 | 
						|
	if not SECTION_TITLES.has_key(section):
 | 
						|
	    raise NoSuchSection(section)
 | 
						|
	maxnum = 0
 | 
						|
	for file in self.list():
 | 
						|
	    sec, num = self.parse(file)
 | 
						|
	    if sec == section:
 | 
						|
		maxnum = max(maxnum, num)
 | 
						|
	sec_num = (section, maxnum+1)
 | 
						|
	file = NEWFILENAME % sec_num
 | 
						|
	return self.entryclass(None, file, sec_num)
 | 
						|
 | 
						|
class FaqWizard:
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
	self.ui = UserInput()
 | 
						|
	self.dir = FaqDir()
 | 
						|
 | 
						|
    def go(self):
 | 
						|
	print 'Content-type: text/html'
 | 
						|
	req = self.ui.req or 'home'
 | 
						|
	mname = 'do_%s' % req
 | 
						|
	try:
 | 
						|
	    meth = getattr(self, mname)
 | 
						|
	except AttributeError:
 | 
						|
	    self.error("Bad request type %s." % `req`)
 | 
						|
	else:
 | 
						|
	    try:
 | 
						|
		meth()
 | 
						|
	    except InvalidFile, exc:
 | 
						|
		self.error("Invalid entry file name %s" % exc.file)
 | 
						|
	    except NoSuchFile, exc:
 | 
						|
		self.error("No entry with file name %s" % exc.file)
 | 
						|
	    except NoSuchSection, exc:
 | 
						|
		self.error("No section number %s" % exc.section)
 | 
						|
	self.epilogue()
 | 
						|
 | 
						|
    def error(self, message, **kw):
 | 
						|
	self.prologue(T_ERROR)
 | 
						|
	emit(message, kw)
 | 
						|
 | 
						|
    def prologue(self, title, entry=None, **kw):
 | 
						|
	emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
 | 
						|
 | 
						|
    def epilogue(self):
 | 
						|
	emit(EPILOGUE)
 | 
						|
 | 
						|
    def do_home(self):
 | 
						|
	self.prologue(T_HOME)
 | 
						|
	emit(HOME)
 | 
						|
 | 
						|
    def do_debug(self):
 | 
						|
	self.prologue("FAQ Wizard Debugging")
 | 
						|
	form = cgi.FieldStorage()
 | 
						|
	cgi.print_form(form)
 | 
						|
        cgi.print_environ(os.environ)
 | 
						|
	cgi.print_directory()
 | 
						|
	cgi.print_arguments()
 | 
						|
 | 
						|
    def do_search(self):
 | 
						|
	query = self.ui.query
 | 
						|
	if not query:
 | 
						|
	    self.error("Empty query string!")
 | 
						|
	    return
 | 
						|
	if self.ui.querytype == 'simple':
 | 
						|
	    for c in '\\.[]?+^$*':
 | 
						|
		if c in query:
 | 
						|
		    query = replace(query, c, '\\'+c)
 | 
						|
	    queries = [query]
 | 
						|
	elif self.ui.querytype in ('anykeywords', 'allkeywords'):
 | 
						|
	    import regsub
 | 
						|
	    words = string.split(regsub.gsub('[^a-zA-Z0-9]+', ' ', query))
 | 
						|
	    if not words:
 | 
						|
		self.error("No keywords specified!")
 | 
						|
		return
 | 
						|
	    words = map(lambda w: '\<%s\>' % w, words)
 | 
						|
	    if self.ui.querytype[:3] == 'any':
 | 
						|
		queries = [string.join(words, '\|')]
 | 
						|
	    else:
 | 
						|
		queries = words
 | 
						|
	else:
 | 
						|
	    # Default to regex
 | 
						|
	    queries = [query]
 | 
						|
	self.prologue(T_SEARCH)
 | 
						|
	progs = []
 | 
						|
	for query in queries:
 | 
						|
	    if self.ui.casefold == 'no':
 | 
						|
		p = regex.compile(query)
 | 
						|
	    else:
 | 
						|
		p = regex.compile(query, regex.casefold)
 | 
						|
	    progs.append(p)
 | 
						|
	hits = []
 | 
						|
	for file in self.dir.list():
 | 
						|
	    try:
 | 
						|
		entry = self.dir.open(file)
 | 
						|
	    except FileError:
 | 
						|
		constants
 | 
						|
	    for p in progs:
 | 
						|
		if p.search(entry.title) < 0 and p.search(entry.body) < 0:
 | 
						|
		    break
 | 
						|
	    else:
 | 
						|
		hits.append(file)
 | 
						|
	if not hits:
 | 
						|
	    emit(NO_HITS, self.ui, count=0)
 | 
						|
	elif len(hits) <= MAXHITS:
 | 
						|
	    if len(hits) == 1:
 | 
						|
		emit(ONE_HIT, count=1)
 | 
						|
	    else:
 | 
						|
		emit(FEW_HITS, count=len(hits))
 | 
						|
	    self.format_all(hits, headers=0)
 | 
						|
	else:
 | 
						|
	    emit(MANY_HITS, count=len(hits))
 | 
						|
	    self.format_index(hits)
 | 
						|
 | 
						|
    def do_all(self):
 | 
						|
	self.prologue(T_ALL)
 | 
						|
	files = self.dir.list()
 | 
						|
	self.last_changed(files)
 | 
						|
	self.format_index(files, localrefs=1)
 | 
						|
	self.format_all(files)
 | 
						|
 | 
						|
    def do_compat(self):
 | 
						|
	files = self.dir.list()
 | 
						|
	emit(COMPAT)
 | 
						|
	self.last_changed(files)
 | 
						|
	self.format_index(files, localrefs=1)
 | 
						|
	self.format_all(files, edit=0)
 | 
						|
	sys.exit(0)			# XXX Hack to suppress epilogue
 | 
						|
 | 
						|
    def last_changed(self, files):
 | 
						|
	latest = 0
 | 
						|
	for file in files:
 | 
						|
	    entry = self.dir.open(file)
 | 
						|
	    if entry:
 | 
						|
		mtime = mtime = entry.getmtime()
 | 
						|
		if mtime > latest:
 | 
						|
		    latest = mtime
 | 
						|
	print time.strftime(LAST_CHANGED, time.localtime(latest))
 | 
						|
	emit(EXPLAIN_MARKS)
 | 
						|
 | 
						|
    def format_all(self, files, edit=1, headers=1):
 | 
						|
	sec = 0
 | 
						|
	for file in files:
 | 
						|
	    try:
 | 
						|
		entry = self.dir.open(file)
 | 
						|
	    except NoSuchFile:
 | 
						|
		continue
 | 
						|
	    if headers and entry.sec != sec:
 | 
						|
		sec = entry.sec
 | 
						|
		try:
 | 
						|
		    title = SECTION_TITLES[sec]
 | 
						|
		except KeyError:
 | 
						|
		    title = "Untitled"
 | 
						|
		emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
 | 
						|
		     sec=sec, title=title)
 | 
						|
	    entry.show(edit=edit)
 | 
						|
 | 
						|
    def do_index(self):
 | 
						|
	self.prologue(T_INDEX)
 | 
						|
	files = self.dir.list()
 | 
						|
	self.last_changed(files)
 | 
						|
	self.format_index(files, add=1)
 | 
						|
 | 
						|
    def format_index(self, files, add=0, localrefs=0):
 | 
						|
	sec = 0
 | 
						|
	for file in files:
 | 
						|
	    try:
 | 
						|
		entry = self.dir.open(file)
 | 
						|
	    except NoSuchFile:
 | 
						|
		continue
 | 
						|
	    if entry.sec != sec:
 | 
						|
		if sec:
 | 
						|
		    if add:
 | 
						|
			emit(INDEX_ADDSECTION, sec=sec)
 | 
						|
		    emit(INDEX_ENDSECTION, sec=sec)
 | 
						|
		sec = entry.sec
 | 
						|
		try:
 | 
						|
		    title = SECTION_TITLES[sec]
 | 
						|
		except KeyError:
 | 
						|
		    title = "Untitled"
 | 
						|
		emit(INDEX_SECTION, sec=sec, title=title)
 | 
						|
	    if localrefs:
 | 
						|
		emit(LOCAL_ENTRY, entry)
 | 
						|
	    else:
 | 
						|
		emit(INDEX_ENTRY, entry)
 | 
						|
	    entry.emit_marks()
 | 
						|
	if sec:
 | 
						|
	    if add:
 | 
						|
		emit(INDEX_ADDSECTION, sec=sec)
 | 
						|
	    emit(INDEX_ENDSECTION, sec=sec)
 | 
						|
 | 
						|
    def do_recent(self):
 | 
						|
	if not self.ui.days:
 | 
						|
	    days = 1
 | 
						|
	else:
 | 
						|
	    days = string.atof(self.ui.days)
 | 
						|
	try:
 | 
						|
	    cutoff = now - days * 24 * 3600
 | 
						|
	except OverflowError:
 | 
						|
	    cutoff = 0
 | 
						|
	list = []
 | 
						|
	for file in self.dir.list():
 | 
						|
	    entry = self.dir.open(file)
 | 
						|
	    if not entry:
 | 
						|
		continue
 | 
						|
	    mtime = entry.getmtime()
 | 
						|
	    if mtime >= cutoff:
 | 
						|
		list.append((mtime, file))
 | 
						|
	list.sort()
 | 
						|
	list.reverse()
 | 
						|
	self.prologue(T_RECENT)
 | 
						|
	if days <= 1:
 | 
						|
	    period = "%.2g hours" % (days*24)
 | 
						|
	else:
 | 
						|
	    period = "%.6g days" % days
 | 
						|
	if not list:
 | 
						|
	    emit(NO_RECENT, period=period)
 | 
						|
	elif len(list) == 1:
 | 
						|
	    emit(ONE_RECENT, period=period)
 | 
						|
	else:
 | 
						|
	    emit(SOME_RECENT, period=period, count=len(list))
 | 
						|
	self.format_all(map(lambda (mtime, file): file, list), headers=0)
 | 
						|
	emit(TAIL_RECENT)
 | 
						|
 | 
						|
    def do_roulette(self):
 | 
						|
	import whrandom
 | 
						|
	files = self.dir.list()
 | 
						|
	if not files: 
 | 
						|
	    self.error("No entries.")
 | 
						|
	    return
 | 
						|
	file = whrandom.choice(files)
 | 
						|
	self.prologue(T_ROULETTE)
 | 
						|
	emit(ROULETTE)
 | 
						|
	self.dir.show(file)
 | 
						|
 | 
						|
    def do_help(self):
 | 
						|
	self.prologue(T_HELP)
 | 
						|
	emit(HELP)
 | 
						|
 | 
						|
    def do_show(self):
 | 
						|
	entry = self.dir.open(self.ui.file)
 | 
						|
	self.prologue(T_SHOW)
 | 
						|
	entry.show()
 | 
						|
 | 
						|
    def do_add(self):
 | 
						|
	self.prologue(T_ADD)
 | 
						|
	emit(ADD_HEAD)
 | 
						|
	sections = SECTION_TITLES.items()
 | 
						|
	sections.sort()
 | 
						|
	for section, title in sections:
 | 
						|
	    emit(ADD_SECTION, section=section, title=title)
 | 
						|
	emit(ADD_TAIL)
 | 
						|
 | 
						|
    def do_delete(self):
 | 
						|
	self.prologue(T_DELETE)
 | 
						|
	emit(DELETE)
 | 
						|
 | 
						|
    def do_log(self):
 | 
						|
	entry = self.dir.open(self.ui.file)
 | 
						|
	self.prologue(T_LOG, entry)
 | 
						|
	emit(LOG, entry)
 | 
						|
	self.rlog(interpolate(SH_RLOG, entry), entry)
 | 
						|
 | 
						|
    def rlog(self, command, entry=None):
 | 
						|
	output = os.popen(command).read()
 | 
						|
	sys.stdout.write('<PRE>')
 | 
						|
	athead = 0
 | 
						|
	lines = string.split(output, '\n')
 | 
						|
	while lines and not lines[-1]:
 | 
						|
	    del lines[-1]
 | 
						|
	if lines:
 | 
						|
	    line = lines[-1]
 | 
						|
	    if line[:1] == '=' and len(line) >= 40 and \
 | 
						|
	       line == line[0]*len(line):
 | 
						|
		del lines[-1]
 | 
						|
	headrev = None
 | 
						|
	for line in lines:
 | 
						|
	    if entry and athead and line[:9] == 'revision ':
 | 
						|
		rev = string.strip(line[9:])
 | 
						|
		mami = revparse(rev)
 | 
						|
		if not mami:
 | 
						|
		    print line
 | 
						|
		else:
 | 
						|
		    emit(REVISIONLINK, entry, rev=rev, line=line)
 | 
						|
		    if mami[1] > 1:
 | 
						|
			prev = "%d.%d" % (mami[0], mami[1]-1)
 | 
						|
			emit(DIFFLINK, entry, prev=prev, rev=rev)
 | 
						|
		    if headrev:
 | 
						|
			emit(DIFFLINK, entry, prev=rev, rev=headrev)
 | 
						|
		    else:
 | 
						|
			headrev = rev
 | 
						|
		    print
 | 
						|
		athead = 0
 | 
						|
	    else:
 | 
						|
		athead = 0
 | 
						|
		if line[:1] == '-' and len(line) >= 20 and \
 | 
						|
		   line == len(line) * line[0]:
 | 
						|
		    athead = 1
 | 
						|
		    sys.stdout.write('<HR>')
 | 
						|
		else:
 | 
						|
		    print line
 | 
						|
	print '</PRE>'
 | 
						|
 | 
						|
    def do_revision(self):
 | 
						|
	entry = self.dir.open(self.ui.file)
 | 
						|
	rev = self.ui.rev
 | 
						|
	mami = revparse(rev)
 | 
						|
	if not mami:
 | 
						|
	    self.error("Invalid revision number: %s." % `rev`)
 | 
						|
	self.prologue(T_REVISION, entry)
 | 
						|
	self.shell(interpolate(SH_REVISION, entry, rev=rev))
 | 
						|
 | 
						|
    def do_diff(self):
 | 
						|
	entry = self.dir.open(self.ui.file)
 | 
						|
	prev = self.ui.prev
 | 
						|
	rev = self.ui.rev
 | 
						|
	mami = revparse(rev)
 | 
						|
	if not mami:
 | 
						|
	    self.error("Invalid revision number: %s." % `rev`)
 | 
						|
	if prev:
 | 
						|
	    if not revparse(prev):
 | 
						|
		self.error("Invalid previous revision number: %s." % `prev`)
 | 
						|
	else:
 | 
						|
	    prev = '%d.%d' % (mami[0], mami[1])
 | 
						|
	self.prologue(T_DIFF, entry)
 | 
						|
	self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
 | 
						|
 | 
						|
    def shell(self, command):
 | 
						|
	output = os.popen(command).read()
 | 
						|
	sys.stdout.write('<PRE>')
 | 
						|
	print escape(output)
 | 
						|
	print '</PRE>'
 | 
						|
 | 
						|
    def do_new(self):
 | 
						|
	entry = self.dir.new(section=string.atoi(self.ui.section))
 | 
						|
	entry.version = '*new*'
 | 
						|
	self.prologue(T_EDIT)
 | 
						|
	emit(EDITHEAD)
 | 
						|
	emit(EDITFORM1, entry, editversion=entry.version)
 | 
						|
	emit(EDITFORM2, entry, load_my_cookie())
 | 
						|
	emit(EDITFORM3)
 | 
						|
	entry.show(edit=0)
 | 
						|
 | 
						|
    def do_edit(self):
 | 
						|
	entry = self.dir.open(self.ui.file)
 | 
						|
	entry.load_version()
 | 
						|
	self.prologue(T_EDIT)
 | 
						|
	emit(EDITHEAD)
 | 
						|
	emit(EDITFORM1, entry, editversion=entry.version)
 | 
						|
	emit(EDITFORM2, entry, load_my_cookie())
 | 
						|
	emit(EDITFORM3)
 | 
						|
	entry.show(edit=0)
 | 
						|
 | 
						|
    def do_review(self):
 | 
						|
	send_my_cookie(self.ui)
 | 
						|
	if self.ui.editversion == '*new*':
 | 
						|
	    sec, num = self.dir.parse(self.ui.file)
 | 
						|
	    entry = self.dir.new(section=sec)
 | 
						|
	    entry.version = "*new*"
 | 
						|
	    if entry.file != self.ui.file:
 | 
						|
		self.error("Commit version conflict!")
 | 
						|
		emit(NEWCONFLICT, self.ui, sec=sec, num=num)
 | 
						|
		return
 | 
						|
	else:
 | 
						|
	    entry = self.dir.open(self.ui.file)
 | 
						|
	    entry.load_version()
 | 
						|
	# Check that the FAQ entry number didn't change
 | 
						|
	if string.split(self.ui.title)[:1] != string.split(entry.title)[:1]:
 | 
						|
	    self.error("Don't change the entry number please!")
 | 
						|
	    return
 | 
						|
	# Check that the edited version is the current version
 | 
						|
	if entry.version != self.ui.editversion:
 | 
						|
	    self.error("Commit version conflict!")
 | 
						|
	    emit(VERSIONCONFLICT, entry, self.ui)
 | 
						|
	    return
 | 
						|
	commit_ok = ((not PASSWORD
 | 
						|
		      or self.ui.password == PASSWORD) 
 | 
						|
		     and self.ui.author
 | 
						|
		     and '@' in self.ui.email
 | 
						|
		     and self.ui.log)
 | 
						|
	if self.ui.commit:
 | 
						|
	    if not commit_ok:
 | 
						|
		self.cantcommit()
 | 
						|
	    else:
 | 
						|
		self.commit(entry)
 | 
						|
	    return
 | 
						|
	self.prologue(T_REVIEW)
 | 
						|
	emit(REVIEWHEAD)
 | 
						|
	entry.body = self.ui.body
 | 
						|
	entry.title = self.ui.title
 | 
						|
	entry.show(edit=0)
 | 
						|
	emit(EDITFORM1, self.ui, entry)
 | 
						|
	if commit_ok:
 | 
						|
	    emit(COMMIT)
 | 
						|
	else:
 | 
						|
	    emit(NOCOMMIT)
 | 
						|
	emit(EDITFORM2, self.ui, entry, load_my_cookie())
 | 
						|
	emit(EDITFORM3)
 | 
						|
 | 
						|
    def cantcommit(self):
 | 
						|
	self.prologue(T_CANTCOMMIT)
 | 
						|
	print CANTCOMMIT_HEAD
 | 
						|
	if not self.ui.passwd:
 | 
						|
	    emit(NEED_PASSWD)
 | 
						|
	if not self.ui.log:
 | 
						|
	    emit(NEED_LOG)
 | 
						|
	if not self.ui.author:
 | 
						|
	    emit(NEED_AUTHOR)
 | 
						|
	if not self.ui.email:
 | 
						|
	    emit(NEED_EMAIL)
 | 
						|
	print CANTCOMMIT_TAIL
 | 
						|
 | 
						|
    def commit(self, entry):
 | 
						|
	file = entry.file
 | 
						|
	# Normalize line endings in body
 | 
						|
	if '\r' in self.ui.body:
 | 
						|
	    import regsub
 | 
						|
	    self.ui.body = regsub.gsub('\r\n?', '\n', self.ui.body)
 | 
						|
	# Normalize whitespace in title
 | 
						|
	self.ui.title = string.join(string.split(self.ui.title))
 | 
						|
	# Check that there were any changes
 | 
						|
	if self.ui.body == entry.body and self.ui.title == entry.title:
 | 
						|
	    self.error("You didn't make any changes!")
 | 
						|
	    return
 | 
						|
	# XXX Should lock here
 | 
						|
	try:
 | 
						|
	    os.unlink(file)
 | 
						|
	except os.error:
 | 
						|
	    pass
 | 
						|
	try:
 | 
						|
	    f = open(file, 'w')
 | 
						|
	except IOError, why:
 | 
						|
	    self.error(CANTWRITE, file=file, why=why)
 | 
						|
	    return
 | 
						|
	date = time.ctime(now)
 | 
						|
	emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
 | 
						|
	f.write('\n')
 | 
						|
	f.write(self.ui.body)
 | 
						|
	f.write('\n')
 | 
						|
	f.close()
 | 
						|
 | 
						|
	import tempfile
 | 
						|
	tfn = tempfile.mktemp()
 | 
						|
	f = open(tfn, 'w')
 | 
						|
	emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
 | 
						|
	f.close()
 | 
						|
 | 
						|
	command = interpolate(
 | 
						|
	    SH_LOCK + '\n' + SH_CHECKIN,
 | 
						|
	    file=file, tfn=tfn)
 | 
						|
 | 
						|
	p = os.popen(command)
 | 
						|
	output = p.read()
 | 
						|
	sts = p.close()
 | 
						|
	# XXX Should unlock here
 | 
						|
	if not sts:
 | 
						|
	    self.prologue(T_COMMITTED)
 | 
						|
	    emit(COMMITTED)
 | 
						|
	else:
 | 
						|
	    self.error(T_COMMITFAILED)
 | 
						|
	    emit(COMMITFAILED, sts=sts)
 | 
						|
	print '<PRE>%s</PRE>' % escape(output)
 | 
						|
 | 
						|
	try:
 | 
						|
	    os.unlink(tfn)
 | 
						|
	except os.error:
 | 
						|
	    pass
 | 
						|
 | 
						|
	entry = self.dir.open(file)
 | 
						|
	entry.show()
 | 
						|
 | 
						|
wiz = FaqWizard()
 | 
						|
wiz.go()
 |