I'm happy with this.

This commit is contained in:
Guido van Rossum 1997-05-26 05:43:29 +00:00
parent 1677e5b5dd
commit ea31ea2859
2 changed files with 478 additions and 291 deletions

View file

@ -1,44 +1,13 @@
# Miscellaneous customization constants
PASSWORD = "Spam" # Edit password. Change this!
FAQCGI = 'faqw.py' # Relative URL of the FAQ cgi script
FAQNAME = "Python FAQ" # Name of the FAQ
OWNERNAME = "GvR" # Name for feedback
OWNEREMAIL = "guido@python.org" # Email for feedback
HOMEURL = "http://www.python.org" # Related home page
HOMENAME = "Python home" # Name of related home page
MAXHITS = 10 # Max #hits to be shown directly
COOKIE_NAME = "Python-FAQ-Wizard" # Name used for Netscape cookie
COOKIE_LIFETIME = 4 *7 * 24 * 3600 # Cookie expiration in seconds
"""FAQ Wizard customization module.
# RCS commands
RCSBINDIR = "/depot/gnu/plat/bin/" # Directory containing RCS commands
SH_RLOG = RCSBINDIR + "rlog %(file)s </dev/null 2>&1"
SH_RLOG_H = RCSBINDIR + "rlog -h %(file)s </dev/null 2>&1"
SH_RDIFF = RCSBINDIR + "rcsdiff -r%(prev)s -r%(rev)s %(file)s </dev/null 2>&1"
SH_LOCK = RCSBINDIR + "rcs -l %(file)s </dev/null 2>&1"
SH_CHECKIN = RCSBINDIR + "ci -u %(file)s <%(tfn)s 2>&1"
Edit this file to customize the FAQ Wizard. For normal purposes, you
should only have to change the FAQ section titles and the small group
of parameters below it.
# Titles for various output pages
T_HOME = FAQNAME + " Wizard 0.2 (alpha)"
T_ERROR = "Sorry, an error occurred"
T_ROULETTE = FAQNAME + " Roulette"
T_ALL = "The Whole " + FAQNAME
T_INDEX = FAQNAME + " Index"
T_SEARCH = FAQNAME + " Search Results"
T_RECENT = "Recently Changed %s Entries" % FAQNAME
T_SHOW = FAQNAME + " Entry"
T_LOG = "RCS log for %s entry" % FAQNAME
T_DIFF = "RCS diff for %s entry" % FAQNAME
T_ADD = "How to add an entry to the " + FAQNAME
T_DELETE = "How to delete an entry from the " + FAQNAME
T_EDIT = FAQNAME + " Edit Wizard"
T_REVIEW = T_EDIT + " - Review Changes"
T_COMMITTED = T_EDIT + " - Changes Committed"
T_COMMITFAILED = T_EDIT + " - Commit Failed"
T_CANTCOMMIT = T_EDIT + " - Commit Rejected"
T_HELP = T_EDIT + " - Help"
"""
# Titles of FAQ sections
SECTION_TITLES = {
1: "General information and availability",
2: "Python in the real world",
@ -49,6 +18,77 @@ SECTION_TITLES = {
7: "Using Python on non-UNIX platforms",
}
# Parameters you definitely want to change
PASSWORD = "Spam" # Editing password
FAQNAME = "Python FAQ" # Name of the FAQ
OWNERNAME = "GvR" # Name for feedback
OWNEREMAIL = "guido@python.org" # Email for feedback
HOMEURL = "http://www.python.org" # Related home page
HOMENAME = "Python home" # Name of related home page
COOKIE_NAME = "Python-FAQ-Wizard" # Name used for Netscape cookie
RCSBINDIR = "/depot/gnu/plat/bin/" # Directory containing RCS commands
# (must end in a slash)
# Parameters you can normally leave alone
FAQCGI = 'faqw.py' # Relative URL of the FAQ cgi script
MAXHITS = 10 # Max #hits to be shown directly
COOKIE_LIFETIME = 28*24*3600 # Cookie expiration in seconds
# (28*24*3600 = 28 days = 4 weeks)
# Regular expression to recognize FAQ entry files: group(1) should be
# the section number, group(2) should be the question number. Both
# should be fixed width so simple-minded sorting yields the right
# order.
OKFILENAME = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
# Format to construct a FAQ entry file name
NEWFILENAME = "faq%02d.%03d.htp"
# Version -- don't change unless you edit faqwiz.py
WIZVERSION = "0.3 (alpha)" # FAQ Wizard version
# ----------------------------------------------------------------------
# Anything below this point normally needn't be changed; you would
# change this if you were to create e.g. a French translation or if
# you just aren't happy with the text generated by the FAQ Wizard.
# Most strings here are subject to substitution (string%dictionary)
# RCS commands
SH_RLOG = RCSBINDIR + "rlog %(file)s </dev/null 2>&1"
SH_RLOG_H = RCSBINDIR + "rlog -h %(file)s </dev/null 2>&1"
SH_RDIFF = RCSBINDIR + "rcsdiff -r%(prev)s -r%(rev)s %(file)s </dev/null 2>&1"
SH_LOCK = RCSBINDIR + "rcs -l %(file)s </dev/null 2>&1"
SH_CHECKIN = RCSBINDIR + "ci -u %(file)s <%(tfn)s 2>&1"
# Titles for various output pages (not subject to substitution)
T_HOME = FAQNAME + " Wizard " + WIZVERSION
T_ERROR = "Sorry, an error occurred"
T_ROULETTE = FAQNAME + " Roulette"
T_ALL = "The Whole " + FAQNAME
T_INDEX = FAQNAME + " Index"
T_SEARCH = FAQNAME + " Search Results"
T_RECENT = "What's New in the " + FAQNAME
T_SHOW = FAQNAME + " Entry"
T_LOG = "RCS log for %s entry" % FAQNAME
T_DIFF = "RCS diff for %s entry" % FAQNAME
T_ADD = "Add an entry to the " + FAQNAME
T_DELETE = "Deleting an entry from the " + FAQNAME
T_EDIT = FAQNAME + " Edit Wizard"
T_REVIEW = T_EDIT + " - Review Changes"
T_COMMITTED = T_EDIT + " - Changes Committed"
T_COMMITFAILED = T_EDIT + " - Commit Failed"
T_CANTCOMMIT = T_EDIT + " - Commit Rejected"
T_HELP = T_EDIT + " - Help"
# Generic prologue and epilogue
PROLOGUE = '''
@ -68,7 +108,7 @@ PROLOGUE = '''
EPILOGUE = '''
<HR>
<A HREF="%(HOMEURL)s">%(HOMENAME)s</A> /
<A HREF="%(FAQCGI)s?req=home">%(FAQNAME)s Wizard</A> /
<A HREF="%(FAQCGI)s?req=home">%(FAQNAME)s Wizard %(WIZVERSION)s</A> /
Feedback to <A HREF="mailto:%(OWNEREMAIL)s">%(OWNERNAME)s</A>
</BODY>
@ -78,18 +118,41 @@ Feedback to <A HREF="mailto:%(OWNEREMAIL)s">%(OWNERNAME)s</A>
# Home page
HOME = """
<H2>Search the %(FAQNAME)s:</H2>
<BLOCKQUOTE>
<FORM ACTION="%(FAQCGI)s">
<INPUT TYPE=text NAME=query>
<INPUT TYPE=submit VALUE="Search"><BR>
(Case insensitive regular expressions.)
<INPUT TYPE=radio NAME=querytype VALUE=simple CHECKED>
Simple string
/
<INPUT TYPE=radio NAME=querytype VALUE=regex>
Regular expression
<BR>
<INPUT TYPE=radio NAME=casefold VALUE=yes CHECKED>
Fold case
/
<INPUT TYPE=radio NAME=casefold VALUE=no>
Case sensitive
<BR>
<INPUT TYPE=hidden NAME=req VALUE=search>
</FORM>
</BLOCKQUOTE>
<HR>
<H2>Other forms of %(FAQNAME)s access:</H2>
<UL>
<LI><A HREF="%(FAQCGI)s?req=index">FAQ index</A>
<LI><A HREF="%(FAQCGI)s?req=all">The whole FAQ</A>
<LI><A HREF="%(FAQCGI)s?req=recent">Recently changed FAQ entries</A>
<LI><A HREF="%(FAQCGI)s?req=recent">What's new in the FAQ?</A>
<LI><A HREF="%(FAQCGI)s?req=roulette">FAQ roulette</A>
<LI><A HREF="%(FAQCGI)s?req=add">Add a FAQ entry</A>
<LI><A HREF="%(FAQCGI)s?req=delete">Delete a FAQ entry</A>
</UL>
"""
@ -98,23 +161,34 @@ HOME = """
INDEX_SECTION = """
<P>
<HR>
<H2>%(sec)d. %(title)s</H2>
<H2>%(sec)s. %(title)s</H2>
<UL>
"""
INDEX_ADDSECTION = """
<P>
<LI><A HREF="%(FAQCGI)s?req=new&amp;section=%(sec)s">Add new entry</A>
(at this point)
"""
INDEX_ENDSECTION = """
</UL>
"""
INDEX_ENTRY = """\
<LI><A HREF="%(FAQCGI)s?req=show&file=%(file)s">%(title)s</A><BR>
<LI><A HREF="%(FAQCGI)s?req=show&amp;file=%(file)s">%(title)s</A><BR>
"""
# Entry formatting
ENTRY_HEADER = """
<HR>
<H2>%(title)s</H2>
"""
ENTRY_FOOTER = """
<A HREF="%(FAQCGI)s?req=edit&file=%(file)s">Edit this entry</A> /
<A HREF="%(FAQCGI)s?req=log&file=%(file)s">Log info</A>
<A HREF="%(FAQCGI)s?req=edit&amp;file=%(file)s">Edit this entry</A> /
<A HREF="%(FAQCGI)s?req=log&amp;file=%(file)s">Log info</A>
"""
ENTRY_LOGINFO = """
@ -133,12 +207,12 @@ Your search matched the following entry:
"""
FEW_HITS = """
Your search matched the following %(count)d entries:
Your search matched the following %(count)s entries:
"""
MANY_HITS = """
Your search matched more than %(MAXHITS)d entries.
The %(count)d matching entries are presented here ordered by section:
Your search matched more than %(MAXHITS)s entries.
The %(count)s matching entries are presented here ordered by section:
"""
# RCS log and diff
@ -149,7 +223,7 @@ previous one.
"""
DIFFLINK = """\
<A HREF="%(FAQCGI)s?req=diff&file=%(file)s&rev=%(rev)s">%(line)s</A>
<A HREF="%(FAQCGI)s?req=diff&amp;file=%(file)s&amp;rev=%(rev)s">%(line)s</A>
"""
# Recently changed entries
@ -159,52 +233,34 @@ NO_RECENT = """
No %(FAQNAME)s entries were changed in the last %(period)s.
"""
ONE_RECENT = """
VIEW_MENU = """
<HR>
View entries changed in the last:
View entries changed in the last...
<UL>
<LI><A HREF="%(FAQCGI)s?req=recent&days=1">24 hours</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=2">2 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=3">3 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=7">week</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=28">4 weeks</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=365250">millennium</A>
<LI><A HREF="%(FAQCGI)s?req=recent&amp;days=1">24 hours</A>
<LI><A HREF="%(FAQCGI)s?req=recent&amp;days=2">2 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&amp;days=3">3 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&amp;days=7">week</A>
<LI><A HREF="%(FAQCGI)s?req=recent&amp;days=28">4 weeks</A>
<LI><A HREF="%(FAQCGI)s?req=recent&amp;days=365250">millennium</A>
</UL>
"""
ONE_RECENT = VIEW_MENU + """
The following %(FAQNAME)s entry was changed in the last %(period)s:
"""
SOME_RECENT = """
<HR>
View entries changed in the last:
<UL>
<LI><A HREF="%(FAQCGI)s?req=recent&days=1">24 hours</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=2">2 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=3">3 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=7">week</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=28">4 weeks</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=365250">millennium</A>
</UL>
The following %(count)d %(FAQNAME)s entries were changed
SOME_RECENT = VIEW_MENU + """
The following %(count)s %(FAQNAME)s entries were changed
in the last %(period)s, most recently changed shown first:
"""
TAIL_RECENT = """
<HR>
View entries changed in the last:
<UL>
<LI><A HREF="%(FAQCGI)s?req=recent&days=1">24 hours</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=2">2 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=3">3 days</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=7">week</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=28">4 weeks</A>
<LI><A HREF="%(FAQCGI)s?req=recent&days=365250">millennium</A>
</UL>
"""
TAIL_RECENT = VIEW_MENU
# Last changed banner on "all" (strftime format)
LAST_CHANGED = "Last changed on %c %Z"
# "Compat" command prologue (no <BODY> tag)
# "Compat" command prologue (this has no <BODY> tag)
COMPAT = """
<H1>The whole %(FAQNAME)s</H1>
"""
@ -261,8 +317,8 @@ Click this button to commit your changes.
"""
NOCOMMIT = """
You can't commit your changes unless you enter a log message, your
name, email addres, and the correct password in the form below.
To commit your changes, please enter a log message, your name, email
addres, and the correct password in the form below.
<HR>
"""
@ -280,6 +336,27 @@ Please use your browser's Back command to correct the form and commit
again.
"""
NEWCONFLICT = """
<P>
You are creating a new entry, but the entry number specified is not
correct.
<P>
The two most common causes of this problem are:
<UL>
<LI>After creating the entry yourself, you went back in your browser,
edited the entry some more, and clicked Commit again.
<LI>Someone else started creating a new entry in the same section and
committed before you did.
</UL>
(It is also possible that the last entry in the section was physically
deleted, but this should not happen except through manual intervention
by the FAQ maintainer.)
<P>
<A HREF="%(FAQCGI)s?req=new&amp;section=%(sec)s">Click here to try
again.</A>
<P>
"""
VERSIONCONFLICT = """
<P>
You edited version %(editversion)s but the current version is %(version)s.
@ -292,8 +369,8 @@ The two most common causes of this problem are:
before you did.
</UL>
<P>
<A HREF="%(FAQCGI)s?req=show&file=%(file)s">Click here to reload the entry
and try again.</A>
<A HREF="%(FAQCGI)s?req=show&amp;file=%(file)s">Click here to reload
the entry and try again.</A>
<P>
"""
@ -328,6 +405,37 @@ COMMITFAILED = """
Exit status %(sts)04x.
"""
# Add/Delete
ADD_HEAD = """
At the moment, new entries can only be added at the end of a section.
This is because the entry numbers are also their
unique identifiers -- it's a bad idea to renumber entries.
<P>
Click on the section to which you want to add a new entry:
<UL>
"""
ADD_SECTION = """\
<LI><A HREF="%(FAQCGI)s?req=new&amp;section=%(section)s">%(section)s. %(title)s</A>
"""
ADD_TAIL = """
</UL>
"""
DELETE = """
At the moment, there's no direct way to delete entries.
This is because the entry numbers are also their
unique identifiers -- it's a bad idea to renumber entries.
<P>
If you really think an entry needs to be deleted,
change the title to "(deleted)" and make the body
empty (keep the entry number in the title though).
"""
# Help file for the FAQ Edit Wizard
HELP = """
Using the %(FAQNAME)s Edit Wizard speaks mostly for itself. Here are
some answers to questions you are likely to ask:

View file

@ -1,6 +1,19 @@
import sys, string, time, os, stat, regex, cgi, faqconf
"""Generic FAQ Wizard.
from cgi import escape
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 in cgi-bin minimal; it's appended at the end of this
file as a string literal.
"""
import sys, string, time, os, stat, regex, cgi, faqconf
from faqconf import * # This imports all uppercase names
class FileError:
def __init__(self, file):
@ -9,34 +22,60 @@ class FileError:
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, '&', '&amp;')
s = replace(s, '<', '&lt;')
s = replace(s, '>', '&gt')
return s
def escapeq(s):
s = escape(s)
import regsub
s = regsub.gsub('"', '&quot;', s)
s = replace(s, '"', '&quot;')
return s
def interpolate(format, entry={}, kwdict={}, **kw):
s = format % MDict(kw, entry, kwdict, faqconf.__dict__)
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 emit(format, entry={}, kwdict={}, file=sys.stdout, **kw):
s = format % MDict(kw, entry, kwdict, faqconf.__dict__)
file.write(s)
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:
import regex
url = '\(http\|ftp\)://[^ \t\r\n]*'
email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
translate_prog = prog = regex.compile(url + "\|" + email)
translate_prog = prog = regex.compile(url + '\|' + email)
else:
prog = translate_prog
i = 0
@ -45,10 +84,10 @@ def translate(text):
j = prog.search(text, i)
if j < 0:
break
list.append(cgi.escape(text[i:j]))
list.append(escape(text[i:j]))
i = j
url = prog.group(0)
while url[-1] in ");:,.?'\"":
while url[-1] in ');:,.?\'"':
url = url[:-1]
url = escape(url)
if ':' in url:
@ -58,7 +97,7 @@ def translate(text):
list.append(repl)
i = i + len(url)
j = len(text)
list.append(cgi.escape(text[i:j]))
list.append(escape(text[i:j]))
return string.join(list, '')
emphasize_prog = None
@ -67,12 +106,9 @@ def emphasize(line):
global emphasize_prog
import regsub
if not emphasize_prog:
import regex
pat = "\*\([a-zA-Z]+\)\*"
emphasize_prog = prog = regex.compile(pat)
else:
prog = emphasize_prog
return regsub.gsub(prog, "<I>\\1</I>", line)
pat = '\*\([a-zA-Z]+\)\*'
emphasize_prog = regex.compile(pat)
return regsub.gsub(emphasize_prog, '<I>\\1</I>', line)
def load_cookies():
if not os.environ.has_key('HTTP_COOKIE'):
@ -90,7 +126,7 @@ def load_cookies():
def load_my_cookie():
cookies = load_cookies()
try:
value = cookies[faqconf.COOKIE_NAME]
value = cookies[COOKIE_NAME]
except KeyError:
return {}
import urllib
@ -105,20 +141,35 @@ def load_my_cookie():
'email': email,
'password': password}
class MDict:
def send_my_cookie(ui):
name = COOKIE_NAME
value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
import urllib
value = urllib.quote(value)
now = time.time()
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)
def __init__(self, *d):
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 ""
return ''
class UserInput:
@ -140,17 +191,50 @@ class UserInput:
def __getitem__(self, key):
return getattr(self, key)
class FaqFormatter:
class FaqEntry:
def __init__(self, entry):
self.entry = entry
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 show(self, edit=1):
entry = self.entry
print "<HR>"
print "<H2>%s</H2>" % escape(entry.title)
emit(ENTRY_HEADER, self)
pre = 0
for line in string.split(entry.body, '\n'):
for line in string.split(self.body, '\n'):
if not string.strip(line):
if pre:
print '</PRE>'
@ -178,57 +262,16 @@ class FaqFormatter:
pre = 0
if edit:
print '<P>'
emit(faqconf.ENTRY_FOOTER, self.entry)
if self.entry.last_changed_date:
emit(faqconf.ENTRY_LOGINFO, self.entry)
emit(ENTRY_FOOTER, self)
if self.last_changed_date:
emit(ENTRY_LOGINFO, self)
print '<P>'
class FaqEntry:
formatterclass = FaqFormatter
def __init__(self, fp, file, sec_num):
import rfc822
self.file = file
self.sec, self.num = sec_num
self.__headers = rfc822.Message(fp)
self.body = string.strip(fp.read())
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 show(self, edit=1):
self.formatterclass(self).show(edit=edit)
def load_version(self):
command = interpolate(faqconf.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
class FaqDir:
entryclass = FaqEntry
__okprog = regex.compile('^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$')
__okprog = regex.compile(OKFILENAME)
def __init__(self, dir=os.curdir):
self.__dir = dir
@ -279,8 +322,17 @@ class FaqDir:
def show(self, file, edit=1):
self.open(file).show(edit=edit)
def new(self, sec):
XXX
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:
@ -289,13 +341,13 @@ class FaqWizard:
self.dir = FaqDir()
def go(self):
print "Content-type: text/html"
req = self.ui.req or "home"
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 %s" % `req`)
self.error("Bad request type %s." % `req`)
else:
try:
meth()
@ -303,29 +355,43 @@ class FaqWizard:
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(faqconf.T_ERROR)
apply(emit, (message,), kw)
self.prologue(T_ERROR)
emit(message, kw)
def prologue(self, title, entry=None, **kw):
emit(faqconf.PROLOGUE, entry, kwdict=kw, title=escape(title))
emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
def epilogue(self):
emit(faqconf.EPILOGUE)
emit(EPILOGUE)
def do_home(self):
self.prologue(faqconf.T_HOME)
emit(faqconf.HOME)
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("No query string")
self.error("Empty query string!")
return
self.prologue(faqconf.T_SEARCH)
if self.ui.casefold == "no":
self.prologue(T_SEARCH)
if self.ui.querytype != 'regex':
for c in '\\.[]?+^$*':
if c in query:
query = replace(query, c, '\\'+c)
if self.ui.casefold == 'no':
p = regex.compile(query)
else:
p = regex.compile(query, regex.casefold)
@ -338,26 +404,26 @@ class FaqWizard:
if p.search(entry.title) >= 0 or p.search(entry.body) >= 0:
hits.append(file)
if not hits:
emit(faqconf.NO_HITS, count=0)
elif len(hits) <= faqconf.MAXHITS:
emit(NO_HITS, self.ui, count=0)
elif len(hits) <= MAXHITS:
if len(hits) == 1:
emit(faqconf.ONE_HIT, count=1)
emit(ONE_HIT, count=1)
else:
emit(faqconf.FEW_HITS, count=len(hits))
emit(FEW_HITS, count=len(hits))
self.format_all(hits)
else:
emit(faqconf.MANY_HITS, count=len(hits))
emit(MANY_HITS, count=len(hits))
self.format_index(hits)
def do_all(self):
self.prologue(faqconf.T_ALL)
self.prologue(T_ALL)
files = self.dir.list()
self.last_changed(files)
self.format_all(files)
def do_compat(self):
files = self.dir.list()
emit(faqconf.COMPAT)
emit(COMPAT)
self.last_changed(files)
self.format_all(files, edit=0)
sys.exit(0)
@ -372,7 +438,7 @@ class FaqWizard:
mtime = st[stat.ST_MTIME]
if mtime > latest:
latest = mtime
print time.strftime(faqconf.LAST_CHANGED,
print time.strftime(LAST_CHANGED,
time.localtime(time.time()))
def format_all(self, files, edit=1):
@ -380,10 +446,10 @@ class FaqWizard:
self.dir.show(file, edit=edit)
def do_index(self):
self.prologue(faqconf.T_INDEX)
self.format_index(self.dir.list())
self.prologue(T_INDEX)
self.format_index(self.dir.list(), add=1)
def format_index(self, files):
def format_index(self, files, add=0):
sec = 0
for file in files:
try:
@ -392,14 +458,16 @@ class FaqWizard:
continue
if entry.sec != sec:
if sec:
emit(faqconf.INDEX_ENDSECTION, sec=sec)
if add:
emit(INDEX_ADDSECTION, sec=sec)
emit(INDEX_ENDSECTION, sec=sec)
sec = entry.sec
emit(faqconf.INDEX_SECTION,
sec=sec,
title=faqconf.SECTION_TITLES[sec])
emit(faqconf.INDEX_ENTRY, entry)
emit(INDEX_SECTION, sec=sec, title=SECTION_TITLES[sec])
emit(INDEX_ENTRY, entry)
if sec:
emit(faqconf.INDEX_ENDSECTION, sec=sec)
if add:
emit(INDEX_ADDSECTION, sec=sec)
emit(INDEX_ENDSECTION, sec=sec)
def do_recent(self):
if not self.ui.days:
@ -422,53 +490,58 @@ class FaqWizard:
list.append((mtime, file))
list.sort()
list.reverse()
self.prologue(faqconf.T_RECENT)
self.prologue(T_RECENT)
if days <= 1:
period = "%.2g hours" % (days*24)
else:
period = "%.6g days" % days
if not list:
emit(faqconf.NO_RECENT, period=period)
emit(NO_RECENT, period=period)
elif len(list) == 1:
emit(faqconf.ONE_RECENT, period=period)
emit(ONE_RECENT, period=period)
else:
emit(faqconf.SOME_RECENT, period=period, count=len(list))
emit(SOME_RECENT, period=period, count=len(list))
self.format_all(map(lambda (mtime, file): file, list))
emit(faqconf.TAIL_RECENT)
emit(TAIL_RECENT)
def do_roulette(self):
self.prologue(faqconf.T_ROULETTE)
self.prologue(T_ROULETTE)
file = self.dir.roulette()
self.dir.show(file)
def do_help(self):
self.prologue(faqconf.T_HELP)
emit(faqconf.HELP)
self.prologue(T_HELP)
emit(HELP)
def do_show(self):
entry = self.dir.open(self.ui.file)
self.prologue("Python FAQ Entry")
self.prologue(T_SHOW)
entry.show()
def do_add(self):
self.prologue(T_ADD)
self.error("Not yet implemented")
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)
self.error("Not yet implemented")
emit(DELETE)
def do_log(self):
entry = self.dir.open(self.ui.file)
self.prologue(faqconf.T_LOG, entry)
emit(faqconf.LOG, entry)
self.rlog(interpolate(faqconf.SH_RLOG, entry), entry)
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>")
sys.stdout.write('<PRE>')
athead = 0
lines = string.split(output, "\n")
lines = string.split(output, '\n')
while lines and not lines[-1]:
del lines[-1]
if lines:
@ -479,8 +552,8 @@ class FaqWizard:
for line in lines:
if entry and athead and line[:9] == 'revision ':
rev = string.strip(line[9:])
if rev != "1.1":
emit(faqconf.DIFFLINK, entry, rev=rev, line=line)
if rev != '1.1':
emit(DIFFLINK, entry, rev=rev, line=line)
else:
print line
athead = 0
@ -489,61 +562,76 @@ class FaqWizard:
if line[:1] == '-' and len(line) >= 20 and \
line == len(line) * line[0]:
athead = 1
sys.stdout.write("<HR>")
sys.stdout.write('<HR>')
else:
print line
print "</PRE>"
print '</PRE>'
def do_diff(self):
entry = self.dir.open(self.ui.file)
rev = self.ui.rev
r = regex.compile(
"^\([1-9][0-9]?[0-9]?\)\.\([1-9][0-9]?[0-9]?[0-9]?\)$")
'^\([1-9][0-9]?[0-9]?\)\.\([1-9][0-9]?[0-9]?[0-9]?\)$')
if r.match(rev) < 0:
self.error("Invalid revision number: %s" % `rev`)
self.error("Invalid revision number: %s." % `rev`)
[major, minor] = map(string.atoi, r.group(1, 2))
if minor == 1:
self.error("No previous revision")
self.error("No previous revision.")
return
prev = "%d.%d" % (major, minor-1)
self.prologue(faqconf.T_DIFF, entry)
self.shell(interpolate(faqconf.SH_RDIFF, entry, rev=rev, prev=prev))
prev = '%d.%d' % (major, minor-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>")
sys.stdout.write('<PRE>')
print escape(output)
print "</PRE>"
print '</PRE>'
def do_new(self):
editor = FaqEditor(self.ui, self.dir.new(self.file))
self.prologue(faqconf.T_NEW)
self.error("Not yet implemented")
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(faqconf.T_EDIT)
emit(faqconf.EDITHEAD)
emit(faqconf.EDITFORM1, entry, editversion=entry.version)
emit(faqconf.EDITFORM2, entry, load_my_cookie(), log=self.ui.log)
emit(faqconf.EDITFORM3)
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):
entry = self.dir.open(self.ui.file)
entry.load_version()
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 FAQ entry number please.")
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("Version conflict.")
emit(faqconf.VERSIONCONFLICT, entry, self.ui)
self.error("Commit version conflict!")
emit(VERSIONCONFLICT, entry, self.ui)
return
commit_ok = ((not faqconf.PASSWORD
or self.ui.password == faqconf.PASSWORD)
commit_ok = ((not PASSWORD
or self.ui.password == PASSWORD)
and self.ui.author
and '@' in self.ui.email
and self.ui.log)
@ -551,40 +639,45 @@ class FaqWizard:
if not commit_ok:
self.cantcommit()
else:
self.commit()
self.commit(entry)
return
self.prologue(faqconf.T_REVIEW)
emit(faqconf.REVIEWHEAD)
self.prologue(T_REVIEW)
emit(REVIEWHEAD)
entry.body = self.ui.body
entry.title = self.ui.title
entry.show(edit=0)
emit(faqconf.EDITFORM1, entry, self.ui)
emit(EDITFORM1, self.ui, entry)
if commit_ok:
emit(faqconf.COMMIT)
emit(COMMIT)
else:
emit(faqconf.NOCOMMIT)
emit(faqconf.EDITFORM2, entry, load_my_cookie(), log=self.ui.log)
emit(faqconf.EDITFORM3)
emit(NOCOMMIT)
emit(EDITFORM2, self.ui, entry, load_my_cookie())
emit(EDITFORM3)
def cantcommit(self):
self.prologue(faqconf.T_CANTCOMMIT)
print faqconf.CANTCOMMIT_HEAD
self.prologue(T_CANTCOMMIT)
print CANTCOMMIT_HEAD
if not self.ui.passwd:
emit(faqconf.NEED_PASSWD)
emit(NEED_PASSWD)
if not self.ui.log:
emit(faqconf.NEED_LOG)
emit(NEED_LOG)
if not self.ui.author:
emit(faqconf.NEED_AUTHOR)
emit(NEED_AUTHOR)
if not self.ui.email:
emit(faqconf.NEED_EMAIL)
print faqconf.CANTCOMMIT_TAIL
emit(NEED_EMAIL)
print CANTCOMMIT_TAIL
def commit(self):
file = self.ui.file
entry = self.dir.open(file)
# Chech that there were any changes
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("No changes.")
self.error("You didn't make any changes!")
return
# XXX Should lock here
try:
@ -592,25 +685,25 @@ class FaqWizard:
except os.error:
pass
try:
f = open(file, "w")
f = open(file, 'w')
except IOError, why:
self.error(faqconf.CANTWRITE, file=file, why=why)
self.error(CANTWRITE, file=file, why=why)
return
date = time.ctime(time.time())
emit(faqconf.FILEHEADER, self.ui, os.environ, date=date, file=f)
f.write("\n")
emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
f.write('\n')
f.write(self.ui.body)
f.write("\n")
f.write('\n')
f.close()
import tempfile
tfn = tempfile.mktemp()
f = open(tfn, "w")
emit(faqconf.LOGHEADER, self.ui, os.environ, date=date, file=f)
f = open(tfn, 'w')
emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
f.close()
command = interpolate(
faqconf.SH_LOCK + "\n" + faqconf.SH_CHECKIN,
SH_LOCK + '\n' + SH_CHECKIN,
file=file, tfn=tfn)
p = os.popen(command)
@ -618,12 +711,12 @@ class FaqWizard:
sts = p.close()
# XXX Should unlock here
if not sts:
self.prologue(faqconf.T_COMMITTED)
emit(faqconf.COMMITTED)
self.prologue(T_COMMITTED)
emit(COMMITTED)
else:
self.error(faqconf.T_COMMITFAILED)
emit(faqconf.COMMITFAILED, sts=sts)
print "<PRE>%s</PRE>" % cgi.escape(output)
self.error(T_COMMITFAILED)
emit(COMMITFAILED, sts=sts)
print '<PRE>%s</PRE>' % escape(output)
try:
os.unlink(tfn)
@ -636,31 +729,17 @@ class FaqWizard:
wiz = FaqWizard()
wiz.go()
# This bootstrap script should be placed in your cgi-bin directory.
# You only need to edit the first two lines: change
# /usr/local/bin/python to where your Python interpreter lives change
# the value for FAQDIR to where your FAQ lives. The faqwiz.py and
# faqconf.py files should live there, too.
BOOTSTRAP = """\
#! /usr/local/bin/python
FAQDIR = "/usr/people/guido/python/FAQ"
# This bootstrap script should be placed in your cgi-bin directory.
# You only need to edit the first two lines (above): Change
# /usr/local/bin/python to where your Python interpreter lives (you
# can't use /usr/bin/env here!); change FAQDIR to where your FAQ
# lives. The faqwiz.py and faqconf.py files should live there, too.
import posix
t1 = posix.times()
import os, sys, time, operator
import sys, os
os.chdir(FAQDIR)
sys.path.insert(0, FAQDIR)
try:
import faqwiz
except SystemExit, n:
sys.exit(n)
except:
t, v, tb = sys.exc_type, sys.exc_value, sys.exc_traceback
print
import cgi
cgi.print_exception(t, v, tb)
t2 = posix.times()
fmt = "<BR>(times: user %.3g, sys %.3g, ch-user %.3g, ch-sys %.3g, real %.3g)"
print fmt % tuple(map(operator.sub, t2, t1))
import faqwiz
"""