/Tools/faqwiz/faqwiz.py

http://unladen-swallow.googlecode.com/ · Python · 841 lines · 757 code · 44 blank · 40 comment · 129 complexity · e52b7b771954627f97bb5030500051e3 MD5 · raw file

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