diff options
Diffstat (limited to 'Tools/faqwiz/faqwiz.py')
-rw-r--r-- | Tools/faqwiz/faqwiz.py | 840 |
1 files changed, 0 insertions, 840 deletions
diff --git a/Tools/faqwiz/faqwiz.py b/Tools/faqwiz/faqwiz.py deleted file mode 100644 index b9ab65d..0000000 --- a/Tools/faqwiz/faqwiz.py +++ /dev/null @@ -1,840 +0,0 @@ -"""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 'HTTP_COOKIE' not in os.environ: - 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.parse - value = urllib.parse.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.parse - value = urllib.parse.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), end=' ') - 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 email - self.__headers = email.message_from_file(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 as 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 section not in SECTION_TITLES: - 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 %r." % (req,)) - else: - try: - meth() - except InvalidFile as exc: - self.error("Invalid entry file name %s" % exc.file) - except NoSuchFile as exc: - self.error("No entry with file name %s" % exc.file) - except NoSuchSection as 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 = [_f for _f in re.split('\W+', query) if _f] - if not words: - self.error("No keywords specified!") - return - words = [r'\b%s\b' % w for w in 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([mtime_file[1] for mtime_file in 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 = sorted(SECTION_TITLES.items()) - 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: %r." % (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: %r." % (rev,)) - if prev: - if not revparse(prev): - self.error("Invalid previous revision number: %r." % (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 as 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=tf) - 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(tf.name) - except os.error: - pass - - entry = self.dir.open(file) - entry.show() - -wiz = FaqWizard() -wiz.go() |