blob: 2b269c2ae1f06737ab2d28600f34a6ce7a5a0762 [file] [log] [blame]
Guido van Rossumea31ea21997-05-26 05:43:29 +00001"""Generic FAQ Wizard.
Guido van Rossum1677e5b1997-05-26 00:07:18 +00002
Guido van Rossumea31ea21997-05-26 05:43:29 +00003This is a CGI program that maintains a user-editable FAQ. It uses RCS
4to keep track of changes to individual FAQ entries. It is fully
5configurable; everything you might want to change when using this
6program to maintain some other FAQ than the Python FAQ is contained in
7the configuration module, faqconf.py.
8
9Note that this is not an executable script; it's an importable module.
10The actual script in cgi-bin minimal; it's appended at the end of this
11file as a string literal.
12
13"""
14
15import sys, string, time, os, stat, regex, cgi, faqconf
16from faqconf import * # This imports all uppercase names
Guido van Rossum1677e5b1997-05-26 00:07:18 +000017
18class FileError:
19 def __init__(self, file):
20 self.file = file
21
22class InvalidFile(FileError):
23 pass
24
Guido van Rossumea31ea21997-05-26 05:43:29 +000025class NoSuchSection(FileError):
26 def __init__(self, section):
27 FileError.__init__(self, NEWFILENAME %(section, 1))
28 self.section = section
29
Guido van Rossum1677e5b1997-05-26 00:07:18 +000030class NoSuchFile(FileError):
31 def __init__(self, file, why=None):
32 FileError.__init__(self, file)
33 self.why = why
34
Guido van Rossumea31ea21997-05-26 05:43:29 +000035def replace(s, old, new):
36 try:
37 return string.replace(s, old, new)
38 except AttributeError:
39 return string.join(string.split(s, old), new)
40
41def escape(s):
42 s = replace(s, '&', '&')
43 s = replace(s, '<', '&lt;')
44 s = replace(s, '>', '&gt')
45 return s
46
Guido van Rossum1677e5b1997-05-26 00:07:18 +000047def escapeq(s):
48 s = escape(s)
Guido van Rossumea31ea21997-05-26 05:43:29 +000049 s = replace(s, '"', '&quot;')
Guido van Rossum1677e5b1997-05-26 00:07:18 +000050 return s
51
Guido van Rossumea31ea21997-05-26 05:43:29 +000052def _interpolate(format, args, kw):
53 try:
54 quote = kw['_quote']
55 except KeyError:
56 quote = 1
57 d = (kw,) + args + (faqconf.__dict__,)
58 m = MagicDict(d, quote)
59 return format % m
Guido van Rossum1677e5b1997-05-26 00:07:18 +000060
Guido van Rossumea31ea21997-05-26 05:43:29 +000061def interpolate(format, *args, **kw):
62 return _interpolate(format, args, kw)
63
64def emit(format, *args, **kw):
65 try:
66 f = kw['_file']
67 except KeyError:
68 f = sys.stdout
69 f.write(_interpolate(format, args, kw))
Guido van Rossum1677e5b1997-05-26 00:07:18 +000070
71translate_prog = None
72
73def translate(text):
74 global translate_prog
75 if not translate_prog:
Guido van Rossum1677e5b1997-05-26 00:07:18 +000076 url = '\(http\|ftp\)://[^ \t\r\n]*'
77 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
Guido van Rossumea31ea21997-05-26 05:43:29 +000078 translate_prog = prog = regex.compile(url + '\|' + email)
Guido van Rossum1677e5b1997-05-26 00:07:18 +000079 else:
80 prog = translate_prog
81 i = 0
82 list = []
83 while 1:
84 j = prog.search(text, i)
85 if j < 0:
86 break
Guido van Rossumea31ea21997-05-26 05:43:29 +000087 list.append(escape(text[i:j]))
Guido van Rossum1677e5b1997-05-26 00:07:18 +000088 i = j
89 url = prog.group(0)
Guido van Rossumea31ea21997-05-26 05:43:29 +000090 while url[-1] in ');:,.?\'"':
Guido van Rossum1677e5b1997-05-26 00:07:18 +000091 url = url[:-1]
92 url = escape(url)
93 if ':' in url:
94 repl = '<A HREF="%s">%s</A>' % (url, url)
95 else:
96 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
97 list.append(repl)
98 i = i + len(url)
99 j = len(text)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000100 list.append(escape(text[i:j]))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000101 return string.join(list, '')
102
103emphasize_prog = None
104
105def emphasize(line):
106 global emphasize_prog
107 import regsub
108 if not emphasize_prog:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000109 pat = '\*\([a-zA-Z]+\)\*'
110 emphasize_prog = regex.compile(pat)
111 return regsub.gsub(emphasize_prog, '<I>\\1</I>', line)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000112
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000113revparse_prog = None
114
115def revparse(rev):
116 global revparse_prog
117 if not revparse_prog:
118 revparse_prog = regex.compile(
119 '^\([1-9][0-9]?[0-9]?\)\.\([1-9][0-9]?[0-9]?[0-9]?\)$')
120 if revparse_prog.match(rev) < 0:
121 return None
122 [major, minor] = map(string.atoi, revparse_prog.group(1, 2))
123 return major, minor
124
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000125def load_cookies():
126 if not os.environ.has_key('HTTP_COOKIE'):
127 return {}
128 raw = os.environ['HTTP_COOKIE']
129 words = map(string.strip, string.split(raw, ';'))
130 cookies = {}
131 for word in words:
132 i = string.find(word, '=')
133 if i >= 0:
134 key, value = word[:i], word[i+1:]
135 cookies[key] = value
136 return cookies
137
138def load_my_cookie():
139 cookies = load_cookies()
140 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000141 value = cookies[COOKIE_NAME]
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000142 except KeyError:
143 return {}
144 import urllib
145 value = urllib.unquote(value)
146 words = string.split(value, '/')
147 while len(words) < 3:
148 words.append('')
149 author = string.join(words[:-2], '/')
150 email = words[-2]
151 password = words[-1]
152 return {'author': author,
153 'email': email,
154 'password': password}
155
Guido van Rossumea31ea21997-05-26 05:43:29 +0000156def send_my_cookie(ui):
157 name = COOKIE_NAME
158 value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
159 import urllib
160 value = urllib.quote(value)
161 now = time.time()
162 then = now + COOKIE_LIFETIME
163 gmt = time.gmtime(then)
164 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
165 print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000166
Guido van Rossumea31ea21997-05-26 05:43:29 +0000167class MagicDict:
168
169 def __init__(self, d, quote):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000170 self.__d = d
Guido van Rossumea31ea21997-05-26 05:43:29 +0000171 self.__quote = quote
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000172
173 def __getitem__(self, key):
174 for d in self.__d:
175 try:
176 value = d[key]
177 if value:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000178 value = str(value)
179 if self.__quote:
180 value = escapeq(value)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000181 return value
182 except KeyError:
183 pass
Guido van Rossumea31ea21997-05-26 05:43:29 +0000184 return ''
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000185
186class UserInput:
187
188 def __init__(self):
189 self.__form = cgi.FieldStorage()
190
191 def __getattr__(self, name):
192 if name[0] == '_':
193 raise AttributeError
194 try:
195 value = self.__form[name].value
196 except (TypeError, KeyError):
197 value = ''
198 else:
199 value = string.strip(value)
200 setattr(self, name, value)
201 return value
202
203 def __getitem__(self, key):
204 return getattr(self, key)
205
Guido van Rossumea31ea21997-05-26 05:43:29 +0000206class FaqEntry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000207
Guido van Rossumea31ea21997-05-26 05:43:29 +0000208 def __init__(self, fp, file, sec_num):
209 self.file = file
210 self.sec, self.num = sec_num
211 if fp:
212 import rfc822
213 self.__headers = rfc822.Message(fp)
214 self.body = string.strip(fp.read())
215 else:
216 self.__headers = {'title': "%d.%d. " % sec_num}
217 self.body = ''
218
219 def __getattr__(self, name):
220 if name[0] == '_':
221 raise AttributeError
222 key = string.join(string.split(name, '_'), '-')
223 try:
224 value = self.__headers[key]
225 except KeyError:
226 value = ''
227 setattr(self, name, value)
228 return value
229
230 def __getitem__(self, key):
231 return getattr(self, key)
232
233 def load_version(self):
234 command = interpolate(SH_RLOG_H, self)
235 p = os.popen(command)
236 version = ''
237 while 1:
238 line = p.readline()
239 if not line:
240 break
241 if line[:5] == 'head:':
242 version = string.strip(line[5:])
243 p.close()
244 self.version = version
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000245
246 def show(self, edit=1):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000247 emit(ENTRY_HEADER, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000248 pre = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000249 for line in string.split(self.body, '\n'):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000250 if not string.strip(line):
251 if pre:
252 print '</PRE>'
253 pre = 0
254 else:
255 print '<P>'
256 else:
257 if line[0] not in string.whitespace:
258 if pre:
259 print '</PRE>'
260 pre = 0
261 else:
262 if not pre:
263 print '<PRE>'
264 pre = 1
265 if '/' in line or '@' in line:
266 line = translate(line)
267 elif '<' in line or '&' in line:
268 line = escape(line)
269 if not pre and '*' in line:
270 line = emphasize(line)
271 print line
272 if pre:
273 print '</PRE>'
274 pre = 0
275 if edit:
276 print '<P>'
Guido van Rossumea31ea21997-05-26 05:43:29 +0000277 emit(ENTRY_FOOTER, self)
278 if self.last_changed_date:
279 emit(ENTRY_LOGINFO, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000280 print '<P>'
281
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000282class FaqDir:
283
284 entryclass = FaqEntry
285
Guido van Rossumea31ea21997-05-26 05:43:29 +0000286 __okprog = regex.compile(OKFILENAME)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000287
288 def __init__(self, dir=os.curdir):
289 self.__dir = dir
290 self.__files = None
291
292 def __fill(self):
293 if self.__files is not None:
294 return
295 self.__files = files = []
296 okprog = self.__okprog
297 for file in os.listdir(self.__dir):
298 if okprog.match(file) >= 0:
299 files.append(file)
300 files.sort()
301
302 def good(self, file):
303 return self.__okprog.match(file) >= 0
304
305 def parse(self, file):
306 if not self.good(file):
307 return None
308 sec, num = self.__okprog.group(1, 2)
309 return string.atoi(sec), string.atoi(num)
310
311 def roulette(self):
312 self.__fill()
313 import whrandom
314 return whrandom.choice(self.__files)
315
316 def list(self):
317 # XXX Caller shouldn't modify result
318 self.__fill()
319 return self.__files
320
321 def open(self, file):
322 sec_num = self.parse(file)
323 if not sec_num:
324 raise InvalidFile(file)
325 try:
326 fp = open(file)
327 except IOError, msg:
328 raise NoSuchFile(file, msg)
329 try:
330 return self.entryclass(fp, file, sec_num)
331 finally:
332 fp.close()
333
334 def show(self, file, edit=1):
335 self.open(file).show(edit=edit)
336
Guido van Rossumea31ea21997-05-26 05:43:29 +0000337 def new(self, section):
338 if not SECTION_TITLES.has_key(section):
339 raise NoSuchSection(section)
340 maxnum = 0
341 for file in self.list():
342 sec, num = self.parse(file)
343 if sec == section:
344 maxnum = max(maxnum, num)
345 sec_num = (section, maxnum+1)
346 file = NEWFILENAME % sec_num
347 return self.entryclass(None, file, sec_num)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000348
349class FaqWizard:
350
351 def __init__(self):
352 self.ui = UserInput()
353 self.dir = FaqDir()
354
355 def go(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000356 print 'Content-type: text/html'
357 req = self.ui.req or 'home'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000358 mname = 'do_%s' % req
359 try:
360 meth = getattr(self, mname)
361 except AttributeError:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000362 self.error("Bad request type %s." % `req`)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000363 else:
364 try:
365 meth()
366 except InvalidFile, exc:
367 self.error("Invalid entry file name %s" % exc.file)
368 except NoSuchFile, exc:
369 self.error("No entry with file name %s" % exc.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000370 except NoSuchSection, exc:
371 self.error("No section number %s" % exc.section)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000372 self.epilogue()
373
374 def error(self, message, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000375 self.prologue(T_ERROR)
376 emit(message, kw)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000377
378 def prologue(self, title, entry=None, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000379 emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000380
381 def epilogue(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000382 emit(EPILOGUE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000383
384 def do_home(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000385 self.prologue(T_HOME)
386 emit(HOME)
387
388 def do_debug(self):
389 self.prologue("FAQ Wizard Debugging")
390 form = cgi.FieldStorage()
391 cgi.print_form(form)
392 cgi.print_environ(os.environ)
393 cgi.print_directory()
394 cgi.print_arguments()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000395
396 def do_search(self):
397 query = self.ui.query
398 if not query:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000399 self.error("Empty query string!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000400 return
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000401 if self.ui.querytype == 'simple':
Guido van Rossumea31ea21997-05-26 05:43:29 +0000402 for c in '\\.[]?+^$*':
403 if c in query:
404 query = replace(query, c, '\\'+c)
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000405 queries = [query]
406 elif self.ui.querytype in ('anykeywords', 'allkeywords'):
407 import regsub
408 words = string.split(regsub.gsub('[^a-zA-Z0-9]+', ' ', query))
409 if not words:
410 self.error("No keywords specified!")
411 return
412 words = map(lambda w: '\<%s\>' % w, words)
413 if self.ui.querytype[:3] == 'any':
414 queries = [string.join(words, '\|')]
415 else:
416 queries = words
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000417 else:
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000418 # Default to regex
419 queries = [query]
420 self.prologue(T_SEARCH)
421 progs = []
422 for query in queries:
423 if self.ui.casefold == 'no':
424 p = regex.compile(query)
425 else:
426 p = regex.compile(query, regex.casefold)
427 progs.append(p)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000428 hits = []
429 for file in self.dir.list():
430 try:
431 entry = self.dir.open(file)
432 except FileError:
433 constants
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000434 for p in progs:
435 if p.search(entry.title) < 0 and p.search(entry.body) < 0:
436 break
437 else:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000438 hits.append(file)
439 if not hits:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000440 emit(NO_HITS, self.ui, count=0)
441 elif len(hits) <= MAXHITS:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000442 if len(hits) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000443 emit(ONE_HIT, count=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000444 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000445 emit(FEW_HITS, count=len(hits))
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000446 self.format_all(hits, headers=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000447 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000448 emit(MANY_HITS, count=len(hits))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000449 self.format_index(hits)
450
451 def do_all(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000452 self.prologue(T_ALL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000453 files = self.dir.list()
454 self.last_changed(files)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000455 self.format_index(files, localrefs=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000456 self.format_all(files)
457
458 def do_compat(self):
459 files = self.dir.list()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000460 emit(COMPAT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000461 self.last_changed(files)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000462 self.format_index(files, localrefs=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000463 self.format_all(files, edit=0)
464 sys.exit(0)
465
466 def last_changed(self, files):
467 latest = 0
468 for file in files:
469 try:
470 st = os.stat(file)
471 except os.error:
472 continue
473 mtime = st[stat.ST_MTIME]
474 if mtime > latest:
475 latest = mtime
Guido van Rossumea31ea21997-05-26 05:43:29 +0000476 print time.strftime(LAST_CHANGED,
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000477 time.localtime(time.time()))
478
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000479 def format_all(self, files, edit=1, headers=1):
480 sec = 0
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000481 for file in files:
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000482 try:
483 entry = self.dir.open(file)
484 except NoSuchFile:
485 continue
486 if headers and entry.sec != sec:
487 sec = entry.sec
488 try:
489 title = SECTION_TITLES[sec]
490 except KeyError:
491 title = "Untitled"
492 emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
493 sec=sec, title=title)
494 entry.show(edit=edit)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000495
496 def do_index(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000497 self.prologue(T_INDEX)
498 self.format_index(self.dir.list(), add=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000499
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000500 def format_index(self, files, add=0, localrefs=0):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000501 sec = 0
502 for file in files:
503 try:
504 entry = self.dir.open(file)
505 except NoSuchFile:
506 continue
507 if entry.sec != sec:
508 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000509 if add:
510 emit(INDEX_ADDSECTION, sec=sec)
511 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000512 sec = entry.sec
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000513 try:
514 title = SECTION_TITLES[sec]
515 except KeyError:
516 title = "Untitled"
517 emit(INDEX_SECTION, sec=sec, title=title)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000518 if localrefs:
519 emit(LOCAL_ENTRY, entry)
520 else:
521 emit(INDEX_ENTRY, entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000522 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000523 if add:
524 emit(INDEX_ADDSECTION, sec=sec)
525 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000526
527 def do_recent(self):
528 if not self.ui.days:
529 days = 1
530 else:
531 days = string.atof(self.ui.days)
532 now = time.time()
533 try:
534 cutoff = now - days * 24 * 3600
535 except OverflowError:
536 cutoff = 0
537 list = []
538 for file in self.dir.list():
539 try:
540 st = os.stat(file)
541 except os.error:
542 continue
543 mtime = st[stat.ST_MTIME]
544 if mtime >= cutoff:
545 list.append((mtime, file))
546 list.sort()
547 list.reverse()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000548 self.prologue(T_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000549 if days <= 1:
550 period = "%.2g hours" % (days*24)
551 else:
552 period = "%.6g days" % days
553 if not list:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000554 emit(NO_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000555 elif len(list) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000556 emit(ONE_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000557 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000558 emit(SOME_RECENT, period=period, count=len(list))
Guido van Rossum1f047721997-05-26 16:02:00 +0000559 self.format_all(map(lambda (mtime, file): file, list), headers=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000560 emit(TAIL_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000561
562 def do_roulette(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000563 self.prologue(T_ROULETTE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000564 file = self.dir.roulette()
565 self.dir.show(file)
566
567 def do_help(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000568 self.prologue(T_HELP)
569 emit(HELP)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000570
571 def do_show(self):
572 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000573 self.prologue(T_SHOW)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000574 entry.show()
575
576 def do_add(self):
577 self.prologue(T_ADD)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000578 emit(ADD_HEAD)
579 sections = SECTION_TITLES.items()
580 sections.sort()
581 for section, title in sections:
582 emit(ADD_SECTION, section=section, title=title)
583 emit(ADD_TAIL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000584
585 def do_delete(self):
586 self.prologue(T_DELETE)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000587 emit(DELETE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000588
589 def do_log(self):
590 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000591 self.prologue(T_LOG, entry)
592 emit(LOG, entry)
593 self.rlog(interpolate(SH_RLOG, entry), entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000594
595 def rlog(self, command, entry=None):
596 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000597 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000598 athead = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000599 lines = string.split(output, '\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000600 while lines and not lines[-1]:
601 del lines[-1]
602 if lines:
603 line = lines[-1]
604 if line[:1] == '=' and len(line) >= 40 and \
605 line == line[0]*len(line):
606 del lines[-1]
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000607 headrev = None
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000608 for line in lines:
609 if entry and athead and line[:9] == 'revision ':
610 rev = string.strip(line[9:])
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000611 mami = revparse(rev)
612 if not mami:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000613 print line
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000614 else:
615 emit(REVISIONLINK, entry, rev=rev, line=line)
616 if mami[1] > 1:
617 prev = "%d.%d" % (mami[0], mami[1]-1)
618 emit(DIFFLINK, entry, prev=prev, rev=rev)
619 if headrev:
620 emit(DIFFLINK, entry, prev=rev, rev=headrev)
621 else:
622 headrev = rev
623 print
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000624 athead = 0
625 else:
626 athead = 0
627 if line[:1] == '-' and len(line) >= 20 and \
628 line == len(line) * line[0]:
629 athead = 1
Guido van Rossumea31ea21997-05-26 05:43:29 +0000630 sys.stdout.write('<HR>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000631 else:
632 print line
Guido van Rossumea31ea21997-05-26 05:43:29 +0000633 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000634
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000635 def do_revision(self):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000636 entry = self.dir.open(self.ui.file)
637 rev = self.ui.rev
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000638 mami = revparse(rev)
639 if not mami:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000640 self.error("Invalid revision number: %s." % `rev`)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000641 self.prologue(T_REVISION, entry)
642 self.shell(interpolate(SH_REVISION, entry, rev=rev))
643
644 def do_diff(self):
645 entry = self.dir.open(self.ui.file)
646 prev = self.ui.prev
647 rev = self.ui.rev
648 mami = revparse(rev)
649 if not mami:
650 self.error("Invalid revision number: %s." % `rev`)
651 if prev:
652 if not revparse(prev):
653 self.error("Invalid previous revision number: %s." % `prev`)
654 else:
655 prev = '%d.%d' % (mami[0], mami[1])
Guido van Rossumea31ea21997-05-26 05:43:29 +0000656 self.prologue(T_DIFF, entry)
657 self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000658
659 def shell(self, command):
660 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000661 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000662 print escape(output)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000663 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000664
665 def do_new(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000666 entry = self.dir.new(section=string.atoi(self.ui.section))
667 entry.version = '*new*'
668 self.prologue(T_EDIT)
669 emit(EDITHEAD)
670 emit(EDITFORM1, entry, editversion=entry.version)
671 emit(EDITFORM2, entry, load_my_cookie())
672 emit(EDITFORM3)
673 entry.show(edit=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000674
675 def do_edit(self):
676 entry = self.dir.open(self.ui.file)
677 entry.load_version()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000678 self.prologue(T_EDIT)
679 emit(EDITHEAD)
680 emit(EDITFORM1, entry, editversion=entry.version)
681 emit(EDITFORM2, entry, load_my_cookie())
682 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000683 entry.show(edit=0)
684
685 def do_review(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000686 send_my_cookie(self.ui)
687 if self.ui.editversion == '*new*':
688 sec, num = self.dir.parse(self.ui.file)
689 entry = self.dir.new(section=sec)
690 entry.version = "*new*"
691 if entry.file != self.ui.file:
692 self.error("Commit version conflict!")
693 emit(NEWCONFLICT, self.ui, sec=sec, num=num)
694 return
695 else:
696 entry = self.dir.open(self.ui.file)
697 entry.load_version()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000698 # Check that the FAQ entry number didn't change
699 if string.split(self.ui.title)[:1] != string.split(entry.title)[:1]:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000700 self.error("Don't change the entry number please!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000701 return
702 # Check that the edited version is the current version
703 if entry.version != self.ui.editversion:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000704 self.error("Commit version conflict!")
705 emit(VERSIONCONFLICT, entry, self.ui)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000706 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000707 commit_ok = ((not PASSWORD
708 or self.ui.password == PASSWORD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000709 and self.ui.author
710 and '@' in self.ui.email
711 and self.ui.log)
712 if self.ui.commit:
713 if not commit_ok:
714 self.cantcommit()
715 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000716 self.commit(entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000717 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000718 self.prologue(T_REVIEW)
719 emit(REVIEWHEAD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000720 entry.body = self.ui.body
721 entry.title = self.ui.title
722 entry.show(edit=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000723 emit(EDITFORM1, self.ui, entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000724 if commit_ok:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000725 emit(COMMIT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000726 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000727 emit(NOCOMMIT)
728 emit(EDITFORM2, self.ui, entry, load_my_cookie())
729 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000730
731 def cantcommit(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000732 self.prologue(T_CANTCOMMIT)
733 print CANTCOMMIT_HEAD
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000734 if not self.ui.passwd:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000735 emit(NEED_PASSWD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000736 if not self.ui.log:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000737 emit(NEED_LOG)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000738 if not self.ui.author:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000739 emit(NEED_AUTHOR)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000740 if not self.ui.email:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000741 emit(NEED_EMAIL)
742 print CANTCOMMIT_TAIL
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000743
Guido van Rossumea31ea21997-05-26 05:43:29 +0000744 def commit(self, entry):
745 file = entry.file
746 # Normalize line endings in body
747 if '\r' in self.ui.body:
748 import regsub
749 self.ui.body = regsub.gsub('\r\n?', '\n', self.ui.body)
750 # Normalize whitespace in title
751 self.ui.title = string.join(string.split(self.ui.title))
752 # Check that there were any changes
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000753 if self.ui.body == entry.body and self.ui.title == entry.title:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000754 self.error("You didn't make any changes!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000755 return
756 # XXX Should lock here
757 try:
758 os.unlink(file)
759 except os.error:
760 pass
761 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000762 f = open(file, 'w')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000763 except IOError, why:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000764 self.error(CANTWRITE, file=file, why=why)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000765 return
766 date = time.ctime(time.time())
Guido van Rossumea31ea21997-05-26 05:43:29 +0000767 emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
768 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000769 f.write(self.ui.body)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000770 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000771 f.close()
772
773 import tempfile
774 tfn = tempfile.mktemp()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000775 f = open(tfn, 'w')
776 emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000777 f.close()
778
779 command = interpolate(
Guido van Rossumea31ea21997-05-26 05:43:29 +0000780 SH_LOCK + '\n' + SH_CHECKIN,
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000781 file=file, tfn=tfn)
782
783 p = os.popen(command)
784 output = p.read()
785 sts = p.close()
786 # XXX Should unlock here
787 if not sts:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000788 self.prologue(T_COMMITTED)
789 emit(COMMITTED)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000790 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000791 self.error(T_COMMITFAILED)
792 emit(COMMITFAILED, sts=sts)
793 print '<PRE>%s</PRE>' % escape(output)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000794
795 try:
796 os.unlink(tfn)
797 except os.error:
798 pass
799
800 entry = self.dir.open(file)
801 entry.show()
802
803wiz = FaqWizard()
804wiz.go()
805
Guido van Rossumea31ea21997-05-26 05:43:29 +0000806# This bootstrap script should be placed in your cgi-bin directory.
807# You only need to edit the first two lines: change
808# /usr/local/bin/python to where your Python interpreter lives change
809# the value for FAQDIR to where your FAQ lives. The faqwiz.py and
810# faqconf.py files should live there, too.
811
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000812BOOTSTRAP = """\
813#! /usr/local/bin/python
814FAQDIR = "/usr/people/guido/python/FAQ"
Guido van Rossumea31ea21997-05-26 05:43:29 +0000815import sys, os
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000816os.chdir(FAQDIR)
817sys.path.insert(0, FAQDIR)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000818import faqwiz
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000819"""