mirror of
https://github.com/python/cpython.git
synced 2024-12-01 11:15:56 +01:00
aaab30e00c
(with one small bugfix in bgen/bgen/scantools.py) This replaces string module functions with string methods for the stuff in the Tools directory. Several uses of string.letters etc. are still remaining.
842 lines
24 KiB
Python
842 lines
24 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, time, os, stat, re, 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 escape(s):
|
|
s = s.replace('&', '&')
|
|
s = s.replace('<', '<')
|
|
s = s.replace('>', '>')
|
|
return s
|
|
|
|
def escapeq(s):
|
|
s = escape(s)
|
|
s = s.replace('"', '"')
|
|
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, pre=0):
|
|
global translate_prog
|
|
if not translate_prog:
|
|
translate_prog = prog = re.compile(
|
|
r'\b(http|ftp|https)://\S+(\b|/)|\b[-.\w]+@[-.\w]+')
|
|
else:
|
|
prog = translate_prog
|
|
i = 0
|
|
list = []
|
|
while 1:
|
|
m = prog.search(text, i)
|
|
if not m:
|
|
break
|
|
j = m.start()
|
|
list.append(escape(text[i:j]))
|
|
i = j
|
|
url = m.group(0)
|
|
while url[-1] in '();:,.?\'"<>':
|
|
url = url[:-1]
|
|
i = i + len(url)
|
|
url = escape(url)
|
|
if not pre or (pre and PROCESS_PREFORMAT):
|
|
if ':' in url:
|
|
repl = '<A HREF="%s">%s</A>' % (url, url)
|
|
else:
|
|
repl = '<A HREF="mailto:%s">%s</A>' % (url, url)
|
|
else:
|
|
repl = url
|
|
list.append(repl)
|
|
j = len(text)
|
|
list.append(escape(text[i:j]))
|
|
return ''.join(list)
|
|
|
|
def emphasize(line):
|
|
return re.sub(r'\*([a-zA-Z]+)\*', r'<I>\1</I>', line)
|
|
|
|
revparse_prog = None
|
|
|
|
def revparse(rev):
|
|
global revparse_prog
|
|
if not revparse_prog:
|
|
revparse_prog = re.compile(r'^(\d{1,3})\.(\d{1,4})$')
|
|
m = revparse_prog.match(rev)
|
|
if not m:
|
|
return None
|
|
[major, minor] = map(int, m.group(1, 2))
|
|
return major, minor
|
|
|
|
logon = 0
|
|
def log(text):
|
|
if logon:
|
|
logfile = open("logfile", "a")
|
|
logfile.write(text + "\n")
|
|
logfile.close()
|
|
|
|
def load_cookies():
|
|
if not os.environ.has_key('HTTP_COOKIE'):
|
|
return {}
|
|
raw = os.environ['HTTP_COOKIE']
|
|
words = [s.strip() for s in raw.split(';')]
|
|
cookies = {}
|
|
for word in words:
|
|
i = word.find('=')
|
|
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 = value.split('/')
|
|
while len(words) < 3:
|
|
words.append('')
|
|
author = '/'.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)
|
|
path = os.environ.get('SCRIPT_NAME', '/cgi-bin/')
|
|
print "Set-Cookie: %s=%s; path=%s;" % (name, value, path),
|
|
print time.strftime("expires=%a, %d-%b-%y %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()
|
|
#log("\n\nbody: " + self.body)
|
|
|
|
def __getattr__(self, name):
|
|
if name[0] == '_':
|
|
raise AttributeError
|
|
try:
|
|
value = self.__form[name].value
|
|
except (TypeError, KeyError):
|
|
value = ''
|
|
else:
|
|
value = value.strip()
|
|
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 = fp.read().strip()
|
|
else:
|
|
self.__headers = {'title': "%d.%d. " % sec_num}
|
|
self.body = ''
|
|
|
|
def __getattr__(self, name):
|
|
if name[0] == '_':
|
|
raise AttributeError
|
|
key = '-'.join(name.split('_'))
|
|
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 = line[5:].strip()
|
|
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 self.body.split('\n'):
|
|
# Allow the user to insert raw html into a FAQ answer
|
|
# (Skip Montanaro, with changes by Guido)
|
|
tag = line.rstrip().lower()
|
|
if tag == '<html>':
|
|
raw = 1
|
|
continue
|
|
if tag == '</html>':
|
|
raw = 0
|
|
continue
|
|
if raw:
|
|
print line
|
|
continue
|
|
if not line.strip():
|
|
if pre:
|
|
print '</PRE>'
|
|
pre = 0
|
|
else:
|
|
print '<P>'
|
|
else:
|
|
if not line[0].isspace():
|
|
if pre:
|
|
print '</PRE>'
|
|
pre = 0
|
|
else:
|
|
if not pre:
|
|
print '<PRE>'
|
|
pre = 1
|
|
if '/' in line or '@' in line:
|
|
line = translate(line, pre)
|
|
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 = re.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 self.__okprog.match(file):
|
|
files.append(file)
|
|
files.sort()
|
|
|
|
def good(self, file):
|
|
return self.__okprog.match(file)
|
|
|
|
def parse(self, file):
|
|
m = self.good(file)
|
|
if not m:
|
|
return None
|
|
sec, num = m.group(1, 2)
|
|
return int(sec), int(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':
|
|
query = re.escape(query)
|
|
queries = [query]
|
|
elif self.ui.querytype in ('anykeywords', 'allkeywords'):
|
|
words = filter(None, re.split('\W+', query))
|
|
if not words:
|
|
self.error("No keywords specified!")
|
|
return
|
|
words = map(lambda w: r'\b%s\b' % w, words)
|
|
if self.ui.querytype[:3] == 'any':
|
|
queries = ['|'.join(words)]
|
|
else:
|
|
# Each of the individual queries must match
|
|
queries = words
|
|
else:
|
|
# Default to regular expression
|
|
queries = [query]
|
|
self.prologue(T_SEARCH)
|
|
progs = []
|
|
for query in queries:
|
|
if self.ui.casefold == 'no':
|
|
p = re.compile(query)
|
|
else:
|
|
p = re.compile(query, re.IGNORECASE)
|
|
progs.append(p)
|
|
hits = []
|
|
for file in self.dir.list():
|
|
try:
|
|
entry = self.dir.open(file)
|
|
except FileError:
|
|
constants
|
|
for p in progs:
|
|
if not p.search(entry.title) and not p.search(entry.body):
|
|
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 = float(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 random
|
|
files = self.dir.list()
|
|
if not files:
|
|
self.error("No entries.")
|
|
return
|
|
file = random.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 = output.split('\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 = line[9:].split()
|
|
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=int(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 self.ui.title.split()[:1] != entry.title.split()[: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_HEAD)
|
|
self.errordetail()
|
|
emit(NOCOMMIT_TAIL)
|
|
emit(EDITFORM2, self.ui, entry, load_my_cookie())
|
|
emit(EDITFORM3)
|
|
|
|
def cantcommit(self):
|
|
self.prologue(T_CANTCOMMIT)
|
|
print CANTCOMMIT_HEAD
|
|
self.errordetail()
|
|
print CANTCOMMIT_TAIL
|
|
|
|
def errordetail(self):
|
|
if PASSWORD and self.ui.password != PASSWORD:
|
|
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)
|
|
|
|
def commit(self, entry):
|
|
file = entry.file
|
|
# Normalize line endings in body
|
|
if '\r' in self.ui.body:
|
|
self.ui.body = re.sub('\r\n?', '\n', self.ui.body)
|
|
# Normalize whitespace in title
|
|
self.ui.title = ' '.join(self.ui.title.split())
|
|
# 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
|
|
|
|
# need to lock here because otherwise the file exists and is not writable (on NT)
|
|
command = interpolate(SH_LOCK, file=file)
|
|
p = os.popen(command)
|
|
output = p.read()
|
|
|
|
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
|
|
tf = tempfile.NamedTemporaryFile()
|
|
emit(LOGHEADER, self.ui, os.environ, date=date, _file=tfn)
|
|
tf.flush()
|
|
tf.seek(0)
|
|
|
|
command = interpolate(SH_CHECKIN, file=file, tfn=tf.name)
|
|
log("\n\n" + command)
|
|
p = os.popen(command)
|
|
output = p.read()
|
|
sts = p.close()
|
|
log("output: " + output)
|
|
log("done: " + str(sts))
|
|
log("TempFile:\n" + tf.read() + "end")
|
|
|
|
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()
|