+++ /dev/null
-"""Generic FAQ Wizard.\r
-\r
-This is a CGI program that maintains a user-editable FAQ. It uses RCS\r
-to keep track of changes to individual FAQ entries. It is fully\r
-configurable; everything you might want to change when using this\r
-program to maintain some other FAQ than the Python FAQ is contained in\r
-the configuration module, faqconf.py.\r
-\r
-Note that this is not an executable script; it's an importable module.\r
-The actual script to place in cgi-bin is faqw.py.\r
-\r
-"""\r
-\r
-import sys, time, os, stat, re, cgi, faqconf\r
-from faqconf import * # This imports all uppercase names\r
-now = time.time()\r
-\r
-class FileError:\r
- def __init__(self, file):\r
- self.file = file\r
-\r
-class InvalidFile(FileError):\r
- pass\r
-\r
-class NoSuchSection(FileError):\r
- def __init__(self, section):\r
- FileError.__init__(self, NEWFILENAME %(section, 1))\r
- self.section = section\r
-\r
-class NoSuchFile(FileError):\r
- def __init__(self, file, why=None):\r
- FileError.__init__(self, file)\r
- self.why = why\r
-\r
-def escape(s):\r
- s = s.replace('&', '&')\r
- s = s.replace('<', '<')\r
- s = s.replace('>', '>')\r
- return s\r
-\r
-def escapeq(s):\r
- s = escape(s)\r
- s = s.replace('"', '"')\r
- return s\r
-\r
-def _interpolate(format, args, kw):\r
- try:\r
- quote = kw['_quote']\r
- except KeyError:\r
- quote = 1\r
- d = (kw,) + args + (faqconf.__dict__,)\r
- m = MagicDict(d, quote)\r
- return format % m\r
-\r
-def interpolate(format, *args, **kw):\r
- return _interpolate(format, args, kw)\r
-\r
-def emit(format, *args, **kw):\r
- try:\r
- f = kw['_file']\r
- except KeyError:\r
- f = sys.stdout\r
- f.write(_interpolate(format, args, kw))\r
-\r
-translate_prog = None\r
-\r
-def translate(text, pre=0):\r
- global translate_prog\r
- if not translate_prog:\r
- translate_prog = prog = re.compile(\r
- r'\b(http|ftp|https)://\S+(\b|/)|\b[-.\w]+@[-.\w]+')\r
- else:\r
- prog = translate_prog\r
- i = 0\r
- list = []\r
- while 1:\r
- m = prog.search(text, i)\r
- if not m:\r
- break\r
- j = m.start()\r
- list.append(escape(text[i:j]))\r
- i = j\r
- url = m.group(0)\r
- while url[-1] in '();:,.?\'"<>':\r
- url = url[:-1]\r
- i = i + len(url)\r
- url = escape(url)\r
- if not pre or (pre and PROCESS_PREFORMAT):\r
- if ':' in url:\r
- repl = '<A HREF="%s">%s</A>' % (url, url)\r
- else:\r
- repl = '<A HREF="mailto:%s">%s</A>' % (url, url)\r
- else:\r
- repl = url\r
- list.append(repl)\r
- j = len(text)\r
- list.append(escape(text[i:j]))\r
- return ''.join(list)\r
-\r
-def emphasize(line):\r
- return re.sub(r'\*([a-zA-Z]+)\*', r'<I>\1</I>', line)\r
-\r
-revparse_prog = None\r
-\r
-def revparse(rev):\r
- global revparse_prog\r
- if not revparse_prog:\r
- revparse_prog = re.compile(r'^(\d{1,3})\.(\d{1,4})$')\r
- m = revparse_prog.match(rev)\r
- if not m:\r
- return None\r
- [major, minor] = map(int, m.group(1, 2))\r
- return major, minor\r
-\r
-logon = 0\r
-def log(text):\r
- if logon:\r
- logfile = open("logfile", "a")\r
- logfile.write(text + "\n")\r
- logfile.close()\r
-\r
-def load_cookies():\r
- if not os.environ.has_key('HTTP_COOKIE'):\r
- return {}\r
- raw = os.environ['HTTP_COOKIE']\r
- words = [s.strip() for s in raw.split(';')]\r
- cookies = {}\r
- for word in words:\r
- i = word.find('=')\r
- if i >= 0:\r
- key, value = word[:i], word[i+1:]\r
- cookies[key] = value\r
- return cookies\r
-\r
-def load_my_cookie():\r
- cookies = load_cookies()\r
- try:\r
- value = cookies[COOKIE_NAME]\r
- except KeyError:\r
- return {}\r
- import urllib\r
- value = urllib.unquote(value)\r
- words = value.split('/')\r
- while len(words) < 3:\r
- words.append('')\r
- author = '/'.join(words[:-2])\r
- email = words[-2]\r
- password = words[-1]\r
- return {'author': author,\r
- 'email': email,\r
- 'password': password}\r
-\r
-def send_my_cookie(ui):\r
- name = COOKIE_NAME\r
- value = "%s/%s/%s" % (ui.author, ui.email, ui.password)\r
- import urllib\r
- value = urllib.quote(value)\r
- then = now + COOKIE_LIFETIME\r
- gmt = time.gmtime(then)\r
- path = os.environ.get('SCRIPT_NAME', '/cgi-bin/')\r
- print "Set-Cookie: %s=%s; path=%s;" % (name, value, path),\r
- print time.strftime("expires=%a, %d-%b-%y %X GMT", gmt)\r
-\r
-class MagicDict:\r
-\r
- def __init__(self, d, quote):\r
- self.__d = d\r
- self.__quote = quote\r
-\r
- def __getitem__(self, key):\r
- for d in self.__d:\r
- try:\r
- value = d[key]\r
- if value:\r
- value = str(value)\r
- if self.__quote:\r
- value = escapeq(value)\r
- return value\r
- except KeyError:\r
- pass\r
- return ''\r
-\r
-class UserInput:\r
-\r
- def __init__(self):\r
- self.__form = cgi.FieldStorage()\r
- #log("\n\nbody: " + self.body)\r
-\r
- def __getattr__(self, name):\r
- if name[0] == '_':\r
- raise AttributeError\r
- try:\r
- value = self.__form[name].value\r
- except (TypeError, KeyError):\r
- value = ''\r
- else:\r
- value = value.strip()\r
- setattr(self, name, value)\r
- return value\r
-\r
- def __getitem__(self, key):\r
- return getattr(self, key)\r
-\r
-class FaqEntry:\r
-\r
- def __init__(self, fp, file, sec_num):\r
- self.file = file\r
- self.sec, self.num = sec_num\r
- if fp:\r
- import rfc822\r
- self.__headers = rfc822.Message(fp)\r
- self.body = fp.read().strip()\r
- else:\r
- self.__headers = {'title': "%d.%d. " % sec_num}\r
- self.body = ''\r
-\r
- def __getattr__(self, name):\r
- if name[0] == '_':\r
- raise AttributeError\r
- key = '-'.join(name.split('_'))\r
- try:\r
- value = self.__headers[key]\r
- except KeyError:\r
- value = ''\r
- setattr(self, name, value)\r
- return value\r
-\r
- def __getitem__(self, key):\r
- return getattr(self, key)\r
-\r
- def load_version(self):\r
- command = interpolate(SH_RLOG_H, self)\r
- p = os.popen(command)\r
- version = ''\r
- while 1:\r
- line = p.readline()\r
- if not line:\r
- break\r
- if line[:5] == 'head:':\r
- version = line[5:].strip()\r
- p.close()\r
- self.version = version\r
-\r
- def getmtime(self):\r
- if not self.last_changed_date:\r
- return 0\r
- try:\r
- return os.stat(self.file)[stat.ST_MTIME]\r
- except os.error:\r
- return 0\r
-\r
- def emit_marks(self):\r
- mtime = self.getmtime()\r
- if mtime >= now - DT_VERY_RECENT:\r
- emit(MARK_VERY_RECENT, self)\r
- elif mtime >= now - DT_RECENT:\r
- emit(MARK_RECENT, self)\r
-\r
- def show(self, edit=1):\r
- emit(ENTRY_HEADER1, self)\r
- self.emit_marks()\r
- emit(ENTRY_HEADER2, self)\r
- pre = 0\r
- raw = 0\r
- for line in self.body.split('\n'):\r
- # Allow the user to insert raw html into a FAQ answer\r
- # (Skip Montanaro, with changes by Guido)\r
- tag = line.rstrip().lower()\r
- if tag == '<html>':\r
- raw = 1\r
- continue\r
- if tag == '</html>':\r
- raw = 0\r
- continue\r
- if raw:\r
- print line\r
- continue\r
- if not line.strip():\r
- if pre:\r
- print '</PRE>'\r
- pre = 0\r
- else:\r
- print '<P>'\r
- else:\r
- if not line[0].isspace():\r
- if pre:\r
- print '</PRE>'\r
- pre = 0\r
- else:\r
- if not pre:\r
- print '<PRE>'\r
- pre = 1\r
- if '/' in line or '@' in line:\r
- line = translate(line, pre)\r
- elif '<' in line or '&' in line:\r
- line = escape(line)\r
- if not pre and '*' in line:\r
- line = emphasize(line)\r
- print line\r
- if pre:\r
- print '</PRE>'\r
- pre = 0\r
- if edit:\r
- print '<P>'\r
- emit(ENTRY_FOOTER, self)\r
- if self.last_changed_date:\r
- emit(ENTRY_LOGINFO, self)\r
- print '<P>'\r
-\r
-class FaqDir:\r
-\r
- entryclass = FaqEntry\r
-\r
- __okprog = re.compile(OKFILENAME)\r
-\r
- def __init__(self, dir=os.curdir):\r
- self.__dir = dir\r
- self.__files = None\r
-\r
- def __fill(self):\r
- if self.__files is not None:\r
- return\r
- self.__files = files = []\r
- okprog = self.__okprog\r
- for file in os.listdir(self.__dir):\r
- if self.__okprog.match(file):\r
- files.append(file)\r
- files.sort()\r
-\r
- def good(self, file):\r
- return self.__okprog.match(file)\r
-\r
- def parse(self, file):\r
- m = self.good(file)\r
- if not m:\r
- return None\r
- sec, num = m.group(1, 2)\r
- return int(sec), int(num)\r
-\r
- def list(self):\r
- # XXX Caller shouldn't modify result\r
- self.__fill()\r
- return self.__files\r
-\r
- def open(self, file):\r
- sec_num = self.parse(file)\r
- if not sec_num:\r
- raise InvalidFile(file)\r
- try:\r
- fp = open(file)\r
- except IOError, msg:\r
- raise NoSuchFile(file, msg)\r
- try:\r
- return self.entryclass(fp, file, sec_num)\r
- finally:\r
- fp.close()\r
-\r
- def show(self, file, edit=1):\r
- self.open(file).show(edit=edit)\r
-\r
- def new(self, section):\r
- if not SECTION_TITLES.has_key(section):\r
- raise NoSuchSection(section)\r
- maxnum = 0\r
- for file in self.list():\r
- sec, num = self.parse(file)\r
- if sec == section:\r
- maxnum = max(maxnum, num)\r
- sec_num = (section, maxnum+1)\r
- file = NEWFILENAME % sec_num\r
- return self.entryclass(None, file, sec_num)\r
-\r
-class FaqWizard:\r
-\r
- def __init__(self):\r
- self.ui = UserInput()\r
- self.dir = FaqDir()\r
-\r
- def go(self):\r
- print 'Content-type: text/html'\r
- req = self.ui.req or 'home'\r
- mname = 'do_%s' % req\r
- try:\r
- meth = getattr(self, mname)\r
- except AttributeError:\r
- self.error("Bad request type %r." % (req,))\r
- else:\r
- try:\r
- meth()\r
- except InvalidFile, exc:\r
- self.error("Invalid entry file name %s" % exc.file)\r
- except NoSuchFile, exc:\r
- self.error("No entry with file name %s" % exc.file)\r
- except NoSuchSection, exc:\r
- self.error("No section number %s" % exc.section)\r
- self.epilogue()\r
-\r
- def error(self, message, **kw):\r
- self.prologue(T_ERROR)\r
- emit(message, kw)\r
-\r
- def prologue(self, title, entry=None, **kw):\r
- emit(PROLOGUE, entry, kwdict=kw, title=escape(title))\r
-\r
- def epilogue(self):\r
- emit(EPILOGUE)\r
-\r
- def do_home(self):\r
- self.prologue(T_HOME)\r
- emit(HOME)\r
-\r
- def do_debug(self):\r
- self.prologue("FAQ Wizard Debugging")\r
- form = cgi.FieldStorage()\r
- cgi.print_form(form)\r
- cgi.print_environ(os.environ)\r
- cgi.print_directory()\r
- cgi.print_arguments()\r
-\r
- def do_search(self):\r
- query = self.ui.query\r
- if not query:\r
- self.error("Empty query string!")\r
- return\r
- if self.ui.querytype == 'simple':\r
- query = re.escape(query)\r
- queries = [query]\r
- elif self.ui.querytype in ('anykeywords', 'allkeywords'):\r
- words = filter(None, re.split('\W+', query))\r
- if not words:\r
- self.error("No keywords specified!")\r
- return\r
- words = map(lambda w: r'\b%s\b' % w, words)\r
- if self.ui.querytype[:3] == 'any':\r
- queries = ['|'.join(words)]\r
- else:\r
- # Each of the individual queries must match\r
- queries = words\r
- else:\r
- # Default to regular expression\r
- queries = [query]\r
- self.prologue(T_SEARCH)\r
- progs = []\r
- for query in queries:\r
- if self.ui.casefold == 'no':\r
- p = re.compile(query)\r
- else:\r
- p = re.compile(query, re.IGNORECASE)\r
- progs.append(p)\r
- hits = []\r
- for file in self.dir.list():\r
- try:\r
- entry = self.dir.open(file)\r
- except FileError:\r
- constants\r
- for p in progs:\r
- if not p.search(entry.title) and not p.search(entry.body):\r
- break\r
- else:\r
- hits.append(file)\r
- if not hits:\r
- emit(NO_HITS, self.ui, count=0)\r
- elif len(hits) <= MAXHITS:\r
- if len(hits) == 1:\r
- emit(ONE_HIT, count=1)\r
- else:\r
- emit(FEW_HITS, count=len(hits))\r
- self.format_all(hits, headers=0)\r
- else:\r
- emit(MANY_HITS, count=len(hits))\r
- self.format_index(hits)\r
-\r
- def do_all(self):\r
- self.prologue(T_ALL)\r
- files = self.dir.list()\r
- self.last_changed(files)\r
- self.format_index(files, localrefs=1)\r
- self.format_all(files)\r
-\r
- def do_compat(self):\r
- files = self.dir.list()\r
- emit(COMPAT)\r
- self.last_changed(files)\r
- self.format_index(files, localrefs=1)\r
- self.format_all(files, edit=0)\r
- sys.exit(0) # XXX Hack to suppress epilogue\r
-\r
- def last_changed(self, files):\r
- latest = 0\r
- for file in files:\r
- entry = self.dir.open(file)\r
- if entry:\r
- mtime = mtime = entry.getmtime()\r
- if mtime > latest:\r
- latest = mtime\r
- print time.strftime(LAST_CHANGED, time.localtime(latest))\r
- emit(EXPLAIN_MARKS)\r
-\r
- def format_all(self, files, edit=1, headers=1):\r
- sec = 0\r
- for file in files:\r
- try:\r
- entry = self.dir.open(file)\r
- except NoSuchFile:\r
- continue\r
- if headers and entry.sec != sec:\r
- sec = entry.sec\r
- try:\r
- title = SECTION_TITLES[sec]\r
- except KeyError:\r
- title = "Untitled"\r
- emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",\r
- sec=sec, title=title)\r
- entry.show(edit=edit)\r
-\r
- def do_index(self):\r
- self.prologue(T_INDEX)\r
- files = self.dir.list()\r
- self.last_changed(files)\r
- self.format_index(files, add=1)\r
-\r
- def format_index(self, files, add=0, localrefs=0):\r
- sec = 0\r
- for file in files:\r
- try:\r
- entry = self.dir.open(file)\r
- except NoSuchFile:\r
- continue\r
- if entry.sec != sec:\r
- if sec:\r
- if add:\r
- emit(INDEX_ADDSECTION, sec=sec)\r
- emit(INDEX_ENDSECTION, sec=sec)\r
- sec = entry.sec\r
- try:\r
- title = SECTION_TITLES[sec]\r
- except KeyError:\r
- title = "Untitled"\r
- emit(INDEX_SECTION, sec=sec, title=title)\r
- if localrefs:\r
- emit(LOCAL_ENTRY, entry)\r
- else:\r
- emit(INDEX_ENTRY, entry)\r
- entry.emit_marks()\r
- if sec:\r
- if add:\r
- emit(INDEX_ADDSECTION, sec=sec)\r
- emit(INDEX_ENDSECTION, sec=sec)\r
-\r
- def do_recent(self):\r
- if not self.ui.days:\r
- days = 1\r
- else:\r
- days = float(self.ui.days)\r
- try:\r
- cutoff = now - days * 24 * 3600\r
- except OverflowError:\r
- cutoff = 0\r
- list = []\r
- for file in self.dir.list():\r
- entry = self.dir.open(file)\r
- if not entry:\r
- continue\r
- mtime = entry.getmtime()\r
- if mtime >= cutoff:\r
- list.append((mtime, file))\r
- list.sort()\r
- list.reverse()\r
- self.prologue(T_RECENT)\r
- if days <= 1:\r
- period = "%.2g hours" % (days*24)\r
- else:\r
- period = "%.6g days" % days\r
- if not list:\r
- emit(NO_RECENT, period=period)\r
- elif len(list) == 1:\r
- emit(ONE_RECENT, period=period)\r
- else:\r
- emit(SOME_RECENT, period=period, count=len(list))\r
- self.format_all(map(lambda (mtime, file): file, list), headers=0)\r
- emit(TAIL_RECENT)\r
-\r
- def do_roulette(self):\r
- import random\r
- files = self.dir.list()\r
- if not files:\r
- self.error("No entries.")\r
- return\r
- file = random.choice(files)\r
- self.prologue(T_ROULETTE)\r
- emit(ROULETTE)\r
- self.dir.show(file)\r
-\r
- def do_help(self):\r
- self.prologue(T_HELP)\r
- emit(HELP)\r
-\r
- def do_show(self):\r
- entry = self.dir.open(self.ui.file)\r
- self.prologue(T_SHOW)\r
- entry.show()\r
-\r
- def do_add(self):\r
- self.prologue(T_ADD)\r
- emit(ADD_HEAD)\r
- sections = SECTION_TITLES.items()\r
- sections.sort()\r
- for section, title in sections:\r
- emit(ADD_SECTION, section=section, title=title)\r
- emit(ADD_TAIL)\r
-\r
- def do_delete(self):\r
- self.prologue(T_DELETE)\r
- emit(DELETE)\r
-\r
- def do_log(self):\r
- entry = self.dir.open(self.ui.file)\r
- self.prologue(T_LOG, entry)\r
- emit(LOG, entry)\r
- self.rlog(interpolate(SH_RLOG, entry), entry)\r
-\r
- def rlog(self, command, entry=None):\r
- output = os.popen(command).read()\r
- sys.stdout.write('<PRE>')\r
- athead = 0\r
- lines = output.split('\n')\r
- while lines and not lines[-1]:\r
- del lines[-1]\r
- if lines:\r
- line = lines[-1]\r
- if line[:1] == '=' and len(line) >= 40 and \\r
- line == line[0]*len(line):\r
- del lines[-1]\r
- headrev = None\r
- for line in lines:\r
- if entry and athead and line[:9] == 'revision ':\r
- rev = line[9:].split()\r
- mami = revparse(rev)\r
- if not mami:\r
- print line\r
- else:\r
- emit(REVISIONLINK, entry, rev=rev, line=line)\r
- if mami[1] > 1:\r
- prev = "%d.%d" % (mami[0], mami[1]-1)\r
- emit(DIFFLINK, entry, prev=prev, rev=rev)\r
- if headrev:\r
- emit(DIFFLINK, entry, prev=rev, rev=headrev)\r
- else:\r
- headrev = rev\r
- print\r
- athead = 0\r
- else:\r
- athead = 0\r
- if line[:1] == '-' and len(line) >= 20 and \\r
- line == len(line) * line[0]:\r
- athead = 1\r
- sys.stdout.write('<HR>')\r
- else:\r
- print line\r
- print '</PRE>'\r
-\r
- def do_revision(self):\r
- entry = self.dir.open(self.ui.file)\r
- rev = self.ui.rev\r
- mami = revparse(rev)\r
- if not mami:\r
- self.error("Invalid revision number: %r." % (rev,))\r
- self.prologue(T_REVISION, entry)\r
- self.shell(interpolate(SH_REVISION, entry, rev=rev))\r
-\r
- def do_diff(self):\r
- entry = self.dir.open(self.ui.file)\r
- prev = self.ui.prev\r
- rev = self.ui.rev\r
- mami = revparse(rev)\r
- if not mami:\r
- self.error("Invalid revision number: %r." % (rev,))\r
- if prev:\r
- if not revparse(prev):\r
- self.error("Invalid previous revision number: %r." % (prev,))\r
- else:\r
- prev = '%d.%d' % (mami[0], mami[1])\r
- self.prologue(T_DIFF, entry)\r
- self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))\r
-\r
- def shell(self, command):\r
- output = os.popen(command).read()\r
- sys.stdout.write('<PRE>')\r
- print escape(output)\r
- print '</PRE>'\r
-\r
- def do_new(self):\r
- entry = self.dir.new(section=int(self.ui.section))\r
- entry.version = '*new*'\r
- self.prologue(T_EDIT)\r
- emit(EDITHEAD)\r
- emit(EDITFORM1, entry, editversion=entry.version)\r
- emit(EDITFORM2, entry, load_my_cookie())\r
- emit(EDITFORM3)\r
- entry.show(edit=0)\r
-\r
- def do_edit(self):\r
- entry = self.dir.open(self.ui.file)\r
- entry.load_version()\r
- self.prologue(T_EDIT)\r
- emit(EDITHEAD)\r
- emit(EDITFORM1, entry, editversion=entry.version)\r
- emit(EDITFORM2, entry, load_my_cookie())\r
- emit(EDITFORM3)\r
- entry.show(edit=0)\r
-\r
- def do_review(self):\r
- send_my_cookie(self.ui)\r
- if self.ui.editversion == '*new*':\r
- sec, num = self.dir.parse(self.ui.file)\r
- entry = self.dir.new(section=sec)\r
- entry.version = "*new*"\r
- if entry.file != self.ui.file:\r
- self.error("Commit version conflict!")\r
- emit(NEWCONFLICT, self.ui, sec=sec, num=num)\r
- return\r
- else:\r
- entry = self.dir.open(self.ui.file)\r
- entry.load_version()\r
- # Check that the FAQ entry number didn't change\r
- if self.ui.title.split()[:1] != entry.title.split()[:1]:\r
- self.error("Don't change the entry number please!")\r
- return\r
- # Check that the edited version is the current version\r
- if entry.version != self.ui.editversion:\r
- self.error("Commit version conflict!")\r
- emit(VERSIONCONFLICT, entry, self.ui)\r
- return\r
- commit_ok = ((not PASSWORD\r
- or self.ui.password == PASSWORD)\r
- and self.ui.author\r
- and '@' in self.ui.email\r
- and self.ui.log)\r
- if self.ui.commit:\r
- if not commit_ok:\r
- self.cantcommit()\r
- else:\r
- self.commit(entry)\r
- return\r
- self.prologue(T_REVIEW)\r
- emit(REVIEWHEAD)\r
- entry.body = self.ui.body\r
- entry.title = self.ui.title\r
- entry.show(edit=0)\r
- emit(EDITFORM1, self.ui, entry)\r
- if commit_ok:\r
- emit(COMMIT)\r
- else:\r
- emit(NOCOMMIT_HEAD)\r
- self.errordetail()\r
- emit(NOCOMMIT_TAIL)\r
- emit(EDITFORM2, self.ui, entry, load_my_cookie())\r
- emit(EDITFORM3)\r
-\r
- def cantcommit(self):\r
- self.prologue(T_CANTCOMMIT)\r
- print CANTCOMMIT_HEAD\r
- self.errordetail()\r
- print CANTCOMMIT_TAIL\r
-\r
- def errordetail(self):\r
- if PASSWORD and self.ui.password != PASSWORD:\r
- emit(NEED_PASSWD)\r
- if not self.ui.log:\r
- emit(NEED_LOG)\r
- if not self.ui.author:\r
- emit(NEED_AUTHOR)\r
- if not self.ui.email:\r
- emit(NEED_EMAIL)\r
-\r
- def commit(self, entry):\r
- file = entry.file\r
- # Normalize line endings in body\r
- if '\r' in self.ui.body:\r
- self.ui.body = re.sub('\r\n?', '\n', self.ui.body)\r
- # Normalize whitespace in title\r
- self.ui.title = ' '.join(self.ui.title.split())\r
- # Check that there were any changes\r
- if self.ui.body == entry.body and self.ui.title == entry.title:\r
- self.error("You didn't make any changes!")\r
- return\r
-\r
- # need to lock here because otherwise the file exists and is not writable (on NT)\r
- command = interpolate(SH_LOCK, file=file)\r
- p = os.popen(command)\r
- output = p.read()\r
-\r
- try:\r
- os.unlink(file)\r
- except os.error:\r
- pass\r
- try:\r
- f = open(file, 'w')\r
- except IOError, why:\r
- self.error(CANTWRITE, file=file, why=why)\r
- return\r
- date = time.ctime(now)\r
- emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)\r
- f.write('\n')\r
- f.write(self.ui.body)\r
- f.write('\n')\r
- f.close()\r
-\r
- import tempfile\r
- tf = tempfile.NamedTemporaryFile()\r
- emit(LOGHEADER, self.ui, os.environ, date=date, _file=tf)\r
- tf.flush()\r
- tf.seek(0)\r
-\r
- command = interpolate(SH_CHECKIN, file=file, tfn=tf.name)\r
- log("\n\n" + command)\r
- p = os.popen(command)\r
- output = p.read()\r
- sts = p.close()\r
- log("output: " + output)\r
- log("done: " + str(sts))\r
- log("TempFile:\n" + tf.read() + "end")\r
-\r
- if not sts:\r
- self.prologue(T_COMMITTED)\r
- emit(COMMITTED)\r
- else:\r
- self.error(T_COMMITFAILED)\r
- emit(COMMITFAILED, sts=sts)\r
- print '<PRE>%s</PRE>' % escape(output)\r
-\r
- try:\r
- os.unlink(tf.name)\r
- except os.error:\r
- pass\r
-\r
- entry = self.dir.open(file)\r
- entry.show()\r
-\r
-wiz = FaqWizard()\r
-wiz.go()\r