mirror of
https://github.com/python/cpython.git
synced 2025-07-17 16:25:18 +00:00

Removed bootstrap script from end of faqwiz.py module. Added instructions to bootstrap script, too. Version bumped to 0.8. Added <html>...</html> feature suggested by Skip Montanaro. Added leading text for Roulette, default to 'Hit Reload ...'. Fix typo in default SRCDIR.
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()
|