home *** CD-ROM | disk | FTP | other *** search
/ AmigActive 15 / AACD15.ISO / AACD / Programming / Python2 / Python20_source / Tools / faqwiz / faqwiz.py < prev    next >
Encoding:
Python Source  |  2000-10-25  |  24.4 KB  |  842 lines

  1. """Generic FAQ Wizard.
  2.  
  3. This is a CGI program that maintains a user-editable FAQ.  It uses RCS
  4. to keep track of changes to individual FAQ entries.  It is fully
  5. configurable; everything you might want to change when using this
  6. program to maintain some other FAQ than the Python FAQ is contained in
  7. the configuration module, faqconf.py.
  8.  
  9. Note that this is not an executable script; it's an importable module.
  10. The actual script to place in cgi-bin is faqw.py.
  11.  
  12. """
  13.  
  14. import sys, string, time, os, stat, re, cgi, faqconf
  15. from faqconf import *                   # This imports all uppercase names
  16. now = time.time()
  17.  
  18. class FileError:
  19.     def __init__(self, file):
  20.         self.file = file
  21.  
  22. class InvalidFile(FileError):
  23.     pass
  24.  
  25. class NoSuchSection(FileError):
  26.     def __init__(self, section):
  27.         FileError.__init__(self, NEWFILENAME %(section, 1))
  28.         self.section = section
  29.  
  30. class NoSuchFile(FileError):
  31.     def __init__(self, file, why=None):
  32.         FileError.__init__(self, file)
  33.         self.why = why
  34.  
  35. def escape(s):
  36.     s = string.replace(s, '&', '&')
  37.     s = string.replace(s, '<', '<')
  38.     s = string.replace(s, '>', '>')
  39.     return s
  40.  
  41. def escapeq(s):
  42.     s = escape(s)
  43.     s = string.replace(s, '"', '"')
  44.     return s
  45.  
  46. def _interpolate(format, args, kw):
  47.     try:
  48.         quote = kw['_quote']
  49.     except KeyError:
  50.         quote = 1
  51.     d = (kw,) + args + (faqconf.__dict__,)
  52.     m = MagicDict(d, quote)
  53.     return format % m
  54.  
  55. def interpolate(format, *args, **kw):
  56.     return _interpolate(format, args, kw)
  57.  
  58. def emit(format, *args, **kw):
  59.     try:
  60.         f = kw['_file']
  61.     except KeyError:
  62.         f = sys.stdout
  63.     f.write(_interpolate(format, args, kw))
  64.  
  65. translate_prog = None
  66.  
  67. def translate(text, pre=0):
  68.     global translate_prog
  69.     if not translate_prog:
  70.         translate_prog = prog = re.compile(
  71.             r'\b(http|ftp|https)://\S+(\b|/)|\b[-.\w]+@[-.\w]+')
  72.     else:
  73.         prog = translate_prog
  74.     i = 0
  75.     list = []
  76.     while 1:
  77.         m = prog.search(text, i)
  78.         if not m:
  79.             break
  80.         j = m.start()
  81.         list.append(escape(text[i:j]))
  82.         i = j
  83.         url = m.group(0)
  84.         while url[-1] in '();:,.?\'"<>':
  85.             url = url[:-1]
  86.         i = i + len(url)
  87.         url = escape(url)
  88.         if not pre or (pre and PROCESS_PREFORMAT):
  89.             if ':' in url:
  90.                 repl = '<A HREF="%s">%s</A>' % (url, url)
  91.             else:
  92.                 repl = '<A HREF="mailto:%s">%s</A>' % (url, url)
  93.         else:
  94.             repl = url
  95.         list.append(repl)
  96.     j = len(text)
  97.     list.append(escape(text[i:j]))
  98.     return string.join(list, '')
  99.  
  100. def emphasize(line):
  101.     return re.sub(r'\*([a-zA-Z]+)\*', r'<I>\1</I>', line)
  102.  
  103. revparse_prog = None
  104.  
  105. def revparse(rev):
  106.     global revparse_prog
  107.     if not revparse_prog:
  108.         revparse_prog = re.compile(r'^(\d{1,3})\.(\d{1,4})$')
  109.     m = revparse_prog.match(rev)
  110.     if not m:
  111.         return None
  112.     [major, minor] = map(string.atoi, m.group(1, 2))
  113.     return major, minor
  114.  
  115. logon = 0
  116. def log(text):
  117.     if logon:
  118.         logfile = open("logfile", "a")
  119.         logfile.write(text + "\n")
  120.         logfile.close()
  121.  
  122. def load_cookies():
  123.     if not os.environ.has_key('HTTP_COOKIE'):
  124.         return {}
  125.     raw = os.environ['HTTP_COOKIE']
  126.     words = map(string.strip, string.split(raw, ';'))
  127.     cookies = {}
  128.     for word in words:
  129.         i = string.find(word, '=')
  130.         if i >= 0:
  131.             key, value = word[:i], word[i+1:]
  132.             cookies[key] = value
  133.     return cookies
  134.  
  135. def load_my_cookie():
  136.     cookies = load_cookies()
  137.     try:
  138.         value = cookies[COOKIE_NAME]
  139.     except KeyError:
  140.         return {}
  141.     import urllib
  142.     value = urllib.unquote(value)
  143.     words = string.split(value, '/')
  144.     while len(words) < 3:
  145.         words.append('')
  146.     author = string.join(words[:-2], '/')
  147.     email = words[-2]
  148.     password = words[-1]
  149.     return {'author': author,
  150.             'email': email,
  151.             'password': password}
  152.  
  153. def send_my_cookie(ui):
  154.     name = COOKIE_NAME
  155.     value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
  156.     import urllib
  157.     value = urllib.quote(value)
  158.     then = now + COOKIE_LIFETIME
  159.     gmt = time.gmtime(then)
  160.     path = os.environ.get('SCRIPT_NAME', '/cgi-bin/')
  161.     print "Set-Cookie: %s=%s; path=%s;" % (name, value, path),
  162.     print time.strftime("expires=%a, %d-%b-%y %X GMT", gmt)
  163.  
  164. class MagicDict:
  165.  
  166.     def __init__(self, d, quote):
  167.         self.__d = d
  168.         self.__quote = quote
  169.  
  170.     def __getitem__(self, key):
  171.         for d in self.__d:
  172.             try:
  173.                 value = d[key]
  174.                 if value:
  175.                     value = str(value)
  176.                     if self.__quote:
  177.                         value = escapeq(value)
  178.                     return value
  179.             except KeyError:
  180.                 pass
  181.         return ''
  182.  
  183. class UserInput:
  184.  
  185.     def __init__(self):
  186.         self.__form = cgi.FieldStorage()
  187.         #log("\n\nbody: " + self.body)
  188.  
  189.     def __getattr__(self, name):
  190.         if name[0] == '_':
  191.             raise AttributeError
  192.         try:
  193.             value = self.__form[name].value
  194.         except (TypeError, KeyError):
  195.             value = ''
  196.         else:
  197.             value = string.strip(value)
  198.         setattr(self, name, value)
  199.         return value
  200.  
  201.     def __getitem__(self, key):
  202.         return getattr(self, key)
  203.  
  204. class FaqEntry:
  205.  
  206.     def __init__(self, fp, file, sec_num):
  207.         self.file = file
  208.         self.sec, self.num = sec_num
  209.         if fp:
  210.             import rfc822
  211.             self.__headers = rfc822.Message(fp)
  212.             self.body = string.strip(fp.read())
  213.         else:
  214.             self.__headers = {'title': "%d.%d. " % sec_num}
  215.             self.body = ''
  216.  
  217.     def __getattr__(self, name):
  218.         if name[0] == '_':
  219.             raise AttributeError
  220.         key = string.join(string.split(name, '_'), '-')
  221.         try:
  222.             value = self.__headers[key]
  223.         except KeyError:
  224.             value = ''
  225.         setattr(self, name, value)
  226.         return value
  227.  
  228.     def __getitem__(self, key):
  229.         return getattr(self, key)
  230.  
  231.     def load_version(self):
  232.         command = interpolate(SH_RLOG_H, self)
  233.         p = os.popen(command)
  234.         version = ''
  235.         while 1:
  236.             line = p.readline()
  237.             if not line:
  238.                 break
  239.             if line[:5] == 'head:':
  240.                 version = string.strip(line[5:])
  241.         p.close()
  242.         self.version = version
  243.  
  244.     def getmtime(self):
  245.         if not self.last_changed_date:
  246.             return 0
  247.         try:
  248.             return os.stat(self.file)[stat.ST_MTIME]
  249.         except os.error:
  250.             return 0
  251.  
  252.     def emit_marks(self):
  253.         mtime = self.getmtime()
  254.         if mtime >= now - DT_VERY_RECENT:
  255.             emit(MARK_VERY_RECENT, self)
  256.         elif mtime >= now - DT_RECENT:
  257.             emit(MARK_RECENT, self)
  258.  
  259.     def show(self, edit=1):
  260.         emit(ENTRY_HEADER1, self)
  261.         self.emit_marks()
  262.         emit(ENTRY_HEADER2, self)
  263.         pre = 0
  264.         raw = 0
  265.         for line in string.split(self.body, '\n'):
  266.             # Allow the user to insert raw html into a FAQ answer
  267.             # (Skip Montanaro, with changes by Guido)
  268.             tag = string.lower(string.rstrip(line))
  269.             if tag == '<html>':
  270.                 raw = 1
  271.                 continue
  272.             if tag == '</html>':
  273.                 raw = 0
  274.                 continue
  275.             if raw:
  276.                 print line
  277.                 continue
  278.             if not string.strip(line):
  279.                 if pre:
  280.                     print '</PRE>'
  281.                     pre = 0
  282.                 else:
  283.                     print '<P>'
  284.             else:
  285.                 if line[0] not in string.whitespace:
  286.                     if pre:
  287.                         print '</PRE>'
  288.                         pre = 0
  289.                 else:
  290.                     if not pre:
  291.                         print '<PRE>'
  292.                         pre = 1
  293.                 if '/' in line or '@' in line:
  294.                     line = translate(line, pre)
  295.                 elif '<' in line or '&' in line:
  296.                     line = escape(line)
  297.                 if not pre and '*' in line:
  298.                     line = emphasize(line)
  299.                 print line
  300.         if pre:
  301.             print '</PRE>'
  302.             pre = 0
  303.         if edit:
  304.             print '<P>'
  305.             emit(ENTRY_FOOTER, self)
  306.             if self.last_changed_date:
  307.                 emit(ENTRY_LOGINFO, self)
  308.         print '<P>'
  309.  
  310. class FaqDir:
  311.  
  312.     entryclass = FaqEntry
  313.  
  314.     __okprog = re.compile(OKFILENAME)
  315.  
  316.     def __init__(self, dir=os.curdir):
  317.         self.__dir = dir
  318.         self.__files = None
  319.  
  320.     def __fill(self):
  321.         if self.__files is not None:
  322.             return
  323.         self.__files = files = []
  324.         okprog = self.__okprog
  325.         for file in os.listdir(self.__dir):
  326.             if self.__okprog.match(file):
  327.                 files.append(file)
  328.         files.sort()
  329.  
  330.     def good(self, file):
  331.         return self.__okprog.match(file)
  332.  
  333.     def parse(self, file):
  334.         m = self.good(file)
  335.         if not m:
  336.             return None
  337.         sec, num = m.group(1, 2)
  338.         return string.atoi(sec), string.atoi(num)
  339.  
  340.     def list(self):
  341.         # XXX Caller shouldn't modify result
  342.         self.__fill()
  343.         return self.__files
  344.  
  345.     def open(self, file):
  346.         sec_num = self.parse(file)
  347.         if not sec_num:
  348.             raise InvalidFile(file)
  349.         try:
  350.             fp = open(file)
  351.         except IOError, msg:
  352.             raise NoSuchFile(file, msg)
  353.         try:
  354.             return self.entryclass(fp, file, sec_num)
  355.         finally:
  356.             fp.close()
  357.  
  358.     def show(self, file, edit=1):
  359.         self.open(file).show(edit=edit)
  360.  
  361.     def new(self, section):
  362.         if not SECTION_TITLES.has_key(section):
  363.             raise NoSuchSection(section)
  364.         maxnum = 0
  365.         for file in self.list():
  366.             sec, num = self.parse(file)
  367.             if sec == section:
  368.                 maxnum = max(maxnum, num)
  369.         sec_num = (section, maxnum+1)
  370.         file = NEWFILENAME % sec_num
  371.         return self.entryclass(None, file, sec_num)
  372.  
  373. class FaqWizard:
  374.  
  375.     def __init__(self):
  376.         self.ui = UserInput()
  377.         self.dir = FaqDir()
  378.  
  379.     def go(self):
  380.         print 'Content-type: text/html'
  381.         req = self.ui.req or 'home'
  382.         mname = 'do_%s' % req
  383.         try:
  384.             meth = getattr(self, mname)
  385.         except AttributeError:
  386.             self.error("Bad request type %s." % `req`)
  387.         else:
  388.             try:
  389.                 meth()
  390.             except InvalidFile, exc:
  391.                 self.error("Invalid entry file name %s" % exc.file)
  392.             except NoSuchFile, exc:
  393.                 self.error("No entry with file name %s" % exc.file)
  394.             except NoSuchSection, exc:
  395.                 self.error("No section number %s" % exc.section)
  396.         self.epilogue()
  397.  
  398.     def error(self, message, **kw):
  399.         self.prologue(T_ERROR)
  400.         emit(message, kw)
  401.  
  402.     def prologue(self, title, entry=None, **kw):
  403.         emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
  404.  
  405.     def epilogue(self):
  406.         emit(EPILOGUE)
  407.  
  408.     def do_home(self):
  409.         self.prologue(T_HOME)
  410.         emit(HOME)
  411.  
  412.     def do_debug(self):
  413.         self.prologue("FAQ Wizard Debugging")
  414.         form = cgi.FieldStorage()
  415.         cgi.print_form(form)
  416.         cgi.print_environ(os.environ)
  417.         cgi.print_directory()
  418.         cgi.print_arguments()
  419.  
  420.     def do_search(self):
  421.         query = self.ui.query
  422.         if not query:
  423.             self.error("Empty query string!")
  424.             return
  425.         if self.ui.querytype == 'simple':
  426.             query = re.escape(query)
  427.             queries = [query]
  428.         elif self.ui.querytype in ('anykeywords', 'allkeywords'):
  429.             words = filter(None, re.split('\W+', query))
  430.             if not words:
  431.                 self.error("No keywords specified!")
  432.                 return
  433.             words = map(lambda w: r'\b%s\b' % w, words)
  434.             if self.ui.querytype[:3] == 'any':
  435.                 queries = [string.join(words, '|')]
  436.             else:
  437.                 # Each of the individual queries must match
  438.                 queries = words
  439.         else:
  440.             # Default to regular expression
  441.             queries = [query]
  442.         self.prologue(T_SEARCH)
  443.         progs = []
  444.         for query in queries:
  445.             if self.ui.casefold == 'no':
  446.                 p = re.compile(query)
  447.             else:
  448.                 p = re.compile(query, re.IGNORECASE)
  449.             progs.append(p)
  450.         hits = []
  451.         for file in self.dir.list():
  452.             try:
  453.                 entry = self.dir.open(file)
  454.             except FileError:
  455.                 constants
  456.             for p in progs:
  457.                 if not p.search(entry.title) and not p.search(entry.body):
  458.                     break
  459.             else:
  460.                 hits.append(file)
  461.         if not hits:
  462.             emit(NO_HITS, self.ui, count=0)
  463.         elif len(hits) <= MAXHITS:
  464.             if len(hits) == 1:
  465.                 emit(ONE_HIT, count=1)
  466.             else:
  467.                 emit(FEW_HITS, count=len(hits))
  468.             self.format_all(hits, headers=0)
  469.         else:
  470.             emit(MANY_HITS, count=len(hits))
  471.             self.format_index(hits)
  472.  
  473.     def do_all(self):
  474.         self.prologue(T_ALL)
  475.         files = self.dir.list()
  476.         self.last_changed(files)
  477.         self.format_index(files, localrefs=1)
  478.         self.format_all(files)
  479.  
  480.     def do_compat(self):
  481.         files = self.dir.list()
  482.         emit(COMPAT)
  483.         self.last_changed(files)
  484.         self.format_index(files, localrefs=1)
  485.         self.format_all(files, edit=0)
  486.         sys.exit(0)                     # XXX Hack to suppress epilogue
  487.  
  488.     def last_changed(self, files):
  489.         latest = 0
  490.         for file in files:
  491.             entry = self.dir.open(file)
  492.             if entry:
  493.                 mtime = mtime = entry.getmtime()
  494.                 if mtime > latest:
  495.                     latest = mtime
  496.         print time.strftime(LAST_CHANGED, time.localtime(latest))
  497.         emit(EXPLAIN_MARKS)
  498.  
  499.     def format_all(self, files, edit=1, headers=1):
  500.         sec = 0
  501.         for file in files:
  502.             try:
  503.                 entry = self.dir.open(file)
  504.             except NoSuchFile:
  505.                 continue
  506.             if headers and entry.sec != sec:
  507.                 sec = entry.sec
  508.                 try:
  509.                     title = SECTION_TITLES[sec]
  510.                 except KeyError:
  511.                     title = "Untitled"
  512.                 emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
  513.                      sec=sec, title=title)
  514.             entry.show(edit=edit)
  515.  
  516.     def do_index(self):
  517.         self.prologue(T_INDEX)
  518.         files = self.dir.list()
  519.         self.last_changed(files)
  520.         self.format_index(files, add=1)
  521.  
  522.     def format_index(self, files, add=0, localrefs=0):
  523.         sec = 0
  524.         for file in files:
  525.             try:
  526.                 entry = self.dir.open(file)
  527.             except NoSuchFile:
  528.                 continue
  529.             if entry.sec != sec:
  530.                 if sec:
  531.                     if add:
  532.                         emit(INDEX_ADDSECTION, sec=sec)
  533.                     emit(INDEX_ENDSECTION, sec=sec)
  534.                 sec = entry.sec
  535.                 try:
  536.                     title = SECTION_TITLES[sec]
  537.                 except KeyError:
  538.                     title = "Untitled"
  539.                 emit(INDEX_SECTION, sec=sec, title=title)
  540.             if localrefs:
  541.                 emit(LOCAL_ENTRY, entry)
  542.             else:
  543.                 emit(INDEX_ENTRY, entry)
  544.             entry.emit_marks()
  545.         if sec:
  546.             if add:
  547.                 emit(INDEX_ADDSECTION, sec=sec)
  548.             emit(INDEX_ENDSECTION, sec=sec)
  549.  
  550.     def do_recent(self):
  551.         if not self.ui.days:
  552.             days = 1
  553.         else:
  554.             days = string.atof(self.ui.days)
  555.         try:
  556.             cutoff = now - days * 24 * 3600
  557.         except OverflowError:
  558.             cutoff = 0
  559.         list = []
  560.         for file in self.dir.list():
  561.             entry = self.dir.open(file)
  562.             if not entry:
  563.                 continue
  564.             mtime = entry.getmtime()
  565.             if mtime >= cutoff:
  566.                 list.append((mtime, file))
  567.         list.sort()
  568.         list.reverse()
  569.         self.prologue(T_RECENT)
  570.         if days <= 1:
  571.             period = "%.2g hours" % (days*24)
  572.         else:
  573.             period = "%.6g days" % days
  574.         if not list:
  575.             emit(NO_RECENT, period=period)
  576.         elif len(list) == 1:
  577.             emit(ONE_RECENT, period=period)
  578.         else:
  579.             emit(SOME_RECENT, period=period, count=len(list))
  580.         self.format_all(map(lambda (mtime, file): file, list), headers=0)
  581.         emit(TAIL_RECENT)
  582.  
  583.     def do_roulette(self):
  584.         import random
  585.         files = self.dir.list()
  586.         if not files: 
  587.             self.error("No entries.")
  588.             return
  589.         file = random.choice(files)
  590.         self.prologue(T_ROULETTE)
  591.         emit(ROULETTE)
  592.         self.dir.show(file)
  593.  
  594.     def do_help(self):
  595.         self.prologue(T_HELP)
  596.         emit(HELP)
  597.  
  598.     def do_show(self):
  599.         entry = self.dir.open(self.ui.file)
  600.         self.prologue(T_SHOW)
  601.         entry.show()
  602.  
  603.     def do_add(self):
  604.         self.prologue(T_ADD)
  605.         emit(ADD_HEAD)
  606.         sections = SECTION_TITLES.items()
  607.         sections.sort()
  608.         for section, title in sections:
  609.             emit(ADD_SECTION, section=section, title=title)
  610.         emit(ADD_TAIL)
  611.  
  612.     def do_delete(self):
  613.         self.prologue(T_DELETE)
  614.         emit(DELETE)
  615.  
  616.     def do_log(self):
  617.         entry = self.dir.open(self.ui.file)
  618.         self.prologue(T_LOG, entry)
  619.         emit(LOG, entry)
  620.         self.rlog(interpolate(SH_RLOG, entry), entry)
  621.  
  622.     def rlog(self, command, entry=None):
  623.         output = os.popen(command).read()
  624.         sys.stdout.write('<PRE>')
  625.         athead = 0
  626.         lines = string.split(output, '\n')
  627.         while lines and not lines[-1]:
  628.             del lines[-1]
  629.         if lines:
  630.             line = lines[-1]
  631.             if line[:1] == '=' and len(line) >= 40 and \
  632.                line == line[0]*len(line):
  633.                 del lines[-1]
  634.         headrev = None
  635.         for line in lines:
  636.             if entry and athead and line[:9] == 'revision ':
  637.                 rev = string.strip(line[9:])
  638.                 mami = revparse(rev)
  639.                 if not mami:
  640.                     print line
  641.                 else:
  642.                     emit(REVISIONLINK, entry, rev=rev, line=line)
  643.                     if mami[1] > 1:
  644.                         prev = "%d.%d" % (mami[0], mami[1]-1)
  645.                         emit(DIFFLINK, entry, prev=prev, rev=rev)
  646.                     if headrev:
  647.                         emit(DIFFLINK, entry, prev=rev, rev=headrev)
  648.                     else:
  649.                         headrev = rev
  650.                     print
  651.                 athead = 0
  652.             else:
  653.                 athead = 0
  654.                 if line[:1] == '-' and len(line) >= 20 and \
  655.                    line == len(line) * line[0]:
  656.                     athead = 1
  657.                     sys.stdout.write('<HR>')
  658.                 else:
  659.                     print line
  660.         print '</PRE>'
  661.  
  662.     def do_revision(self):
  663.         entry = self.dir.open(self.ui.file)
  664.         rev = self.ui.rev
  665.         mami = revparse(rev)
  666.         if not mami:
  667.             self.error("Invalid revision number: %s." % `rev`)
  668.         self.prologue(T_REVISION, entry)
  669.         self.shell(interpolate(SH_REVISION, entry, rev=rev))
  670.  
  671.     def do_diff(self):
  672.         entry = self.dir.open(self.ui.file)
  673.         prev = self.ui.prev
  674.         rev = self.ui.rev
  675.         mami = revparse(rev)
  676.         if not mami:
  677.             self.error("Invalid revision number: %s." % `rev`)
  678.         if prev:
  679.             if not revparse(prev):
  680.                 self.error("Invalid previous revision number: %s." % `prev`)
  681.         else:
  682.             prev = '%d.%d' % (mami[0], mami[1])
  683.         self.prologue(T_DIFF, entry)
  684.         self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
  685.  
  686.     def shell(self, command):
  687.         output = os.popen(command).read()
  688.         sys.stdout.write('<PRE>')
  689.         print escape(output)
  690.         print '</PRE>'
  691.  
  692.     def do_new(self):
  693.         entry = self.dir.new(section=string.atoi(self.ui.section))
  694.         entry.version = '*new*'
  695.         self.prologue(T_EDIT)
  696.         emit(EDITHEAD)
  697.         emit(EDITFORM1, entry, editversion=entry.version)
  698.         emit(EDITFORM2, entry, load_my_cookie())
  699.         emit(EDITFORM3)
  700.         entry.show(edit=0)
  701.  
  702.     def do_edit(self):
  703.         entry = self.dir.open(self.ui.file)
  704.         entry.load_version()
  705.         self.prologue(T_EDIT)
  706.         emit(EDITHEAD)
  707.         emit(EDITFORM1, entry, editversion=entry.version)
  708.         emit(EDITFORM2, entry, load_my_cookie())
  709.         emit(EDITFORM3)
  710.         entry.show(edit=0)
  711.  
  712.     def do_review(self):
  713.         send_my_cookie(self.ui)
  714.         if self.ui.editversion == '*new*':
  715.             sec, num = self.dir.parse(self.ui.file)
  716.             entry = self.dir.new(section=sec)
  717.             entry.version = "*new*"
  718.             if entry.file != self.ui.file:
  719.                 self.error("Commit version conflict!")
  720.                 emit(NEWCONFLICT, self.ui, sec=sec, num=num)
  721.                 return
  722.         else:
  723.             entry = self.dir.open(self.ui.file)
  724.             entry.load_version()
  725.         # Check that the FAQ entry number didn't change
  726.         if string.split(self.ui.title)[:1] != string.split(entry.title)[:1]:
  727.             self.error("Don't change the entry number please!")
  728.             return
  729.         # Check that the edited version is the current version
  730.         if entry.version != self.ui.editversion:
  731.             self.error("Commit version conflict!")
  732.             emit(VERSIONCONFLICT, entry, self.ui)
  733.             return
  734.         commit_ok = ((not PASSWORD
  735.                       or self.ui.password == PASSWORD) 
  736.                      and self.ui.author
  737.                      and '@' in self.ui.email
  738.                      and self.ui.log)
  739.         if self.ui.commit:
  740.             if not commit_ok:
  741.                 self.cantcommit()
  742.             else:
  743.                 self.commit(entry)
  744.             return
  745.         self.prologue(T_REVIEW)
  746.         emit(REVIEWHEAD)
  747.         entry.body = self.ui.body
  748.         entry.title = self.ui.title
  749.         entry.show(edit=0)
  750.         emit(EDITFORM1, self.ui, entry)
  751.         if commit_ok:
  752.             emit(COMMIT)
  753.         else:
  754.             emit(NOCOMMIT_HEAD)
  755.             self.errordetail()
  756.             emit(NOCOMMIT_TAIL)
  757.         emit(EDITFORM2, self.ui, entry, load_my_cookie())
  758.         emit(EDITFORM3)
  759.  
  760.     def cantcommit(self):
  761.         self.prologue(T_CANTCOMMIT)
  762.         print CANTCOMMIT_HEAD
  763.         self.errordetail()
  764.         print CANTCOMMIT_TAIL
  765.  
  766.     def errordetail(self):
  767.         if PASSWORD and self.ui.password != PASSWORD:
  768.             emit(NEED_PASSWD)
  769.         if not self.ui.log:
  770.             emit(NEED_LOG)
  771.         if not self.ui.author:
  772.             emit(NEED_AUTHOR)
  773.         if not self.ui.email:
  774.             emit(NEED_EMAIL)
  775.  
  776.     def commit(self, entry):
  777.         file = entry.file
  778.         # Normalize line endings in body
  779.         if '\r' in self.ui.body:
  780.             self.ui.body = re.sub('\r\n?', '\n', self.ui.body)
  781.         # Normalize whitespace in title
  782.         self.ui.title = string.join(string.split(self.ui.title))
  783.         # Check that there were any changes
  784.         if self.ui.body == entry.body and self.ui.title == entry.title:
  785.             self.error("You didn't make any changes!")
  786.             return
  787.  
  788.         # need to lock here because otherwise the file exists and is not writable (on NT)
  789.         command = interpolate(SH_LOCK, file=file)
  790.         p = os.popen(command)
  791.         output = p.read()
  792.  
  793.         try:
  794.             os.unlink(file)
  795.         except os.error:
  796.             pass
  797.         try:
  798.             f = open(file, 'w')
  799.         except IOError, why:
  800.             self.error(CANTWRITE, file=file, why=why)
  801.             return
  802.         date = time.ctime(now)
  803.         emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
  804.         f.write('\n')
  805.         f.write(self.ui.body)
  806.         f.write('\n')
  807.         f.close()
  808.  
  809.         import tempfile
  810.         tfn = tempfile.mktemp()
  811.         f = open(tfn, 'w')
  812.         emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
  813.         f.close()
  814.  
  815.         command = interpolate(SH_CHECKIN, file=file, tfn=tfn)
  816.         log("\n\n" + command)
  817.         p = os.popen(command)
  818.         output = p.read()
  819.         sts = p.close()
  820.         log("output: " + output)
  821.         log("done: " + str(sts))
  822.         log("TempFile:\n" + open(tfn).read() + "end")
  823.         
  824.         if not sts:
  825.             self.prologue(T_COMMITTED)
  826.             emit(COMMITTED)
  827.         else:
  828.             self.error(T_COMMITFAILED)
  829.             emit(COMMITFAILED, sts=sts)
  830.         print '<PRE>%s</PRE>' % escape(output)
  831.  
  832.         try:
  833.             os.unlink(tfn)
  834.         except os.error:
  835.             pass
  836.  
  837.         entry = self.dir.open(file)
  838.         entry.show()
  839.  
  840. wiz = FaqWizard()
  841. wiz.go()
  842.