blob: 17a79d6ced96a8365a5ea59974ac0599a9ba4ed3 [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
113def load_cookies():
114 if not os.environ.has_key('HTTP_COOKIE'):
115 return {}
116 raw = os.environ['HTTP_COOKIE']
117 words = map(string.strip, string.split(raw, ';'))
118 cookies = {}
119 for word in words:
120 i = string.find(word, '=')
121 if i >= 0:
122 key, value = word[:i], word[i+1:]
123 cookies[key] = value
124 return cookies
125
126def load_my_cookie():
127 cookies = load_cookies()
128 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000129 value = cookies[COOKIE_NAME]
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000130 except KeyError:
131 return {}
132 import urllib
133 value = urllib.unquote(value)
134 words = string.split(value, '/')
135 while len(words) < 3:
136 words.append('')
137 author = string.join(words[:-2], '/')
138 email = words[-2]
139 password = words[-1]
140 return {'author': author,
141 'email': email,
142 'password': password}
143
Guido van Rossumea31ea21997-05-26 05:43:29 +0000144def send_my_cookie(ui):
145 name = COOKIE_NAME
146 value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
147 import urllib
148 value = urllib.quote(value)
149 now = time.time()
150 then = now + COOKIE_LIFETIME
151 gmt = time.gmtime(then)
152 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
153 print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000154
Guido van Rossumea31ea21997-05-26 05:43:29 +0000155class MagicDict:
156
157 def __init__(self, d, quote):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000158 self.__d = d
Guido van Rossumea31ea21997-05-26 05:43:29 +0000159 self.__quote = quote
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000160
161 def __getitem__(self, key):
162 for d in self.__d:
163 try:
164 value = d[key]
165 if value:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000166 value = str(value)
167 if self.__quote:
168 value = escapeq(value)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000169 return value
170 except KeyError:
171 pass
Guido van Rossumea31ea21997-05-26 05:43:29 +0000172 return ''
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000173
174class UserInput:
175
176 def __init__(self):
177 self.__form = cgi.FieldStorage()
178
179 def __getattr__(self, name):
180 if name[0] == '_':
181 raise AttributeError
182 try:
183 value = self.__form[name].value
184 except (TypeError, KeyError):
185 value = ''
186 else:
187 value = string.strip(value)
188 setattr(self, name, value)
189 return value
190
191 def __getitem__(self, key):
192 return getattr(self, key)
193
Guido van Rossumea31ea21997-05-26 05:43:29 +0000194class FaqEntry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000195
Guido van Rossumea31ea21997-05-26 05:43:29 +0000196 def __init__(self, fp, file, sec_num):
197 self.file = file
198 self.sec, self.num = sec_num
199 if fp:
200 import rfc822
201 self.__headers = rfc822.Message(fp)
202 self.body = string.strip(fp.read())
203 else:
204 self.__headers = {'title': "%d.%d. " % sec_num}
205 self.body = ''
206
207 def __getattr__(self, name):
208 if name[0] == '_':
209 raise AttributeError
210 key = string.join(string.split(name, '_'), '-')
211 try:
212 value = self.__headers[key]
213 except KeyError:
214 value = ''
215 setattr(self, name, value)
216 return value
217
218 def __getitem__(self, key):
219 return getattr(self, key)
220
221 def load_version(self):
222 command = interpolate(SH_RLOG_H, self)
223 p = os.popen(command)
224 version = ''
225 while 1:
226 line = p.readline()
227 if not line:
228 break
229 if line[:5] == 'head:':
230 version = string.strip(line[5:])
231 p.close()
232 self.version = version
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000233
234 def show(self, edit=1):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000235 emit(ENTRY_HEADER, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000236 pre = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000237 for line in string.split(self.body, '\n'):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000238 if not string.strip(line):
239 if pre:
240 print '</PRE>'
241 pre = 0
242 else:
243 print '<P>'
244 else:
245 if line[0] not in string.whitespace:
246 if pre:
247 print '</PRE>'
248 pre = 0
249 else:
250 if not pre:
251 print '<PRE>'
252 pre = 1
253 if '/' in line or '@' in line:
254 line = translate(line)
255 elif '<' in line or '&' in line:
256 line = escape(line)
257 if not pre and '*' in line:
258 line = emphasize(line)
259 print line
260 if pre:
261 print '</PRE>'
262 pre = 0
263 if edit:
264 print '<P>'
Guido van Rossumea31ea21997-05-26 05:43:29 +0000265 emit(ENTRY_FOOTER, self)
266 if self.last_changed_date:
267 emit(ENTRY_LOGINFO, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000268 print '<P>'
269
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000270class FaqDir:
271
272 entryclass = FaqEntry
273
Guido van Rossumea31ea21997-05-26 05:43:29 +0000274 __okprog = regex.compile(OKFILENAME)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000275
276 def __init__(self, dir=os.curdir):
277 self.__dir = dir
278 self.__files = None
279
280 def __fill(self):
281 if self.__files is not None:
282 return
283 self.__files = files = []
284 okprog = self.__okprog
285 for file in os.listdir(self.__dir):
286 if okprog.match(file) >= 0:
287 files.append(file)
288 files.sort()
289
290 def good(self, file):
291 return self.__okprog.match(file) >= 0
292
293 def parse(self, file):
294 if not self.good(file):
295 return None
296 sec, num = self.__okprog.group(1, 2)
297 return string.atoi(sec), string.atoi(num)
298
299 def roulette(self):
300 self.__fill()
301 import whrandom
302 return whrandom.choice(self.__files)
303
304 def list(self):
305 # XXX Caller shouldn't modify result
306 self.__fill()
307 return self.__files
308
309 def open(self, file):
310 sec_num = self.parse(file)
311 if not sec_num:
312 raise InvalidFile(file)
313 try:
314 fp = open(file)
315 except IOError, msg:
316 raise NoSuchFile(file, msg)
317 try:
318 return self.entryclass(fp, file, sec_num)
319 finally:
320 fp.close()
321
322 def show(self, file, edit=1):
323 self.open(file).show(edit=edit)
324
Guido van Rossumea31ea21997-05-26 05:43:29 +0000325 def new(self, section):
326 if not SECTION_TITLES.has_key(section):
327 raise NoSuchSection(section)
328 maxnum = 0
329 for file in self.list():
330 sec, num = self.parse(file)
331 if sec == section:
332 maxnum = max(maxnum, num)
333 sec_num = (section, maxnum+1)
334 file = NEWFILENAME % sec_num
335 return self.entryclass(None, file, sec_num)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000336
337class FaqWizard:
338
339 def __init__(self):
340 self.ui = UserInput()
341 self.dir = FaqDir()
342
343 def go(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000344 print 'Content-type: text/html'
345 req = self.ui.req or 'home'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000346 mname = 'do_%s' % req
347 try:
348 meth = getattr(self, mname)
349 except AttributeError:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000350 self.error("Bad request type %s." % `req`)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000351 else:
352 try:
353 meth()
354 except InvalidFile, exc:
355 self.error("Invalid entry file name %s" % exc.file)
356 except NoSuchFile, exc:
357 self.error("No entry with file name %s" % exc.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000358 except NoSuchSection, exc:
359 self.error("No section number %s" % exc.section)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000360 self.epilogue()
361
362 def error(self, message, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000363 self.prologue(T_ERROR)
364 emit(message, kw)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000365
366 def prologue(self, title, entry=None, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000367 emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000368
369 def epilogue(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000370 emit(EPILOGUE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000371
372 def do_home(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000373 self.prologue(T_HOME)
374 emit(HOME)
375
376 def do_debug(self):
377 self.prologue("FAQ Wizard Debugging")
378 form = cgi.FieldStorage()
379 cgi.print_form(form)
380 cgi.print_environ(os.environ)
381 cgi.print_directory()
382 cgi.print_arguments()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000383
384 def do_search(self):
385 query = self.ui.query
386 if not query:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000387 self.error("Empty query string!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000388 return
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000389 if self.ui.querytype == 'simple':
Guido van Rossumea31ea21997-05-26 05:43:29 +0000390 for c in '\\.[]?+^$*':
391 if c in query:
392 query = replace(query, c, '\\'+c)
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000393 queries = [query]
394 elif self.ui.querytype in ('anykeywords', 'allkeywords'):
395 import regsub
396 words = string.split(regsub.gsub('[^a-zA-Z0-9]+', ' ', query))
397 if not words:
398 self.error("No keywords specified!")
399 return
400 words = map(lambda w: '\<%s\>' % w, words)
401 if self.ui.querytype[:3] == 'any':
402 queries = [string.join(words, '\|')]
403 else:
404 queries = words
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000405 else:
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000406 # Default to regex
407 queries = [query]
408 self.prologue(T_SEARCH)
409 progs = []
410 for query in queries:
411 if self.ui.casefold == 'no':
412 p = regex.compile(query)
413 else:
414 p = regex.compile(query, regex.casefold)
415 progs.append(p)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000416 hits = []
417 for file in self.dir.list():
418 try:
419 entry = self.dir.open(file)
420 except FileError:
421 constants
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000422 for p in progs:
423 if p.search(entry.title) < 0 and p.search(entry.body) < 0:
424 break
425 else:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000426 hits.append(file)
427 if not hits:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000428 emit(NO_HITS, self.ui, count=0)
429 elif len(hits) <= MAXHITS:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000430 if len(hits) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000431 emit(ONE_HIT, count=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000432 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000433 emit(FEW_HITS, count=len(hits))
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000434 self.format_all(hits, headers=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000435 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000436 emit(MANY_HITS, count=len(hits))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000437 self.format_index(hits)
438
439 def do_all(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000440 self.prologue(T_ALL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000441 files = self.dir.list()
442 self.last_changed(files)
443 self.format_all(files)
444
445 def do_compat(self):
446 files = self.dir.list()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000447 emit(COMPAT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000448 self.last_changed(files)
449 self.format_all(files, edit=0)
450 sys.exit(0)
451
452 def last_changed(self, files):
453 latest = 0
454 for file in files:
455 try:
456 st = os.stat(file)
457 except os.error:
458 continue
459 mtime = st[stat.ST_MTIME]
460 if mtime > latest:
461 latest = mtime
Guido van Rossumea31ea21997-05-26 05:43:29 +0000462 print time.strftime(LAST_CHANGED,
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000463 time.localtime(time.time()))
464
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000465 def format_all(self, files, edit=1, headers=1):
466 sec = 0
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000467 for file in files:
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000468 try:
469 entry = self.dir.open(file)
470 except NoSuchFile:
471 continue
472 if headers and entry.sec != sec:
473 sec = entry.sec
474 try:
475 title = SECTION_TITLES[sec]
476 except KeyError:
477 title = "Untitled"
478 emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
479 sec=sec, title=title)
480 entry.show(edit=edit)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000481
482 def do_index(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000483 self.prologue(T_INDEX)
484 self.format_index(self.dir.list(), add=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000485
Guido van Rossumea31ea21997-05-26 05:43:29 +0000486 def format_index(self, files, add=0):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000487 sec = 0
488 for file in files:
489 try:
490 entry = self.dir.open(file)
491 except NoSuchFile:
492 continue
493 if entry.sec != sec:
494 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000495 if add:
496 emit(INDEX_ADDSECTION, sec=sec)
497 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000498 sec = entry.sec
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000499 try:
500 title = SECTION_TITLES[sec]
501 except KeyError:
502 title = "Untitled"
503 emit(INDEX_SECTION, sec=sec, title=title)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000504 emit(INDEX_ENTRY, entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000505 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000506 if add:
507 emit(INDEX_ADDSECTION, sec=sec)
508 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000509
510 def do_recent(self):
511 if not self.ui.days:
512 days = 1
513 else:
514 days = string.atof(self.ui.days)
515 now = time.time()
516 try:
517 cutoff = now - days * 24 * 3600
518 except OverflowError:
519 cutoff = 0
520 list = []
521 for file in self.dir.list():
522 try:
523 st = os.stat(file)
524 except os.error:
525 continue
526 mtime = st[stat.ST_MTIME]
527 if mtime >= cutoff:
528 list.append((mtime, file))
529 list.sort()
530 list.reverse()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000531 self.prologue(T_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000532 if days <= 1:
533 period = "%.2g hours" % (days*24)
534 else:
535 period = "%.6g days" % days
536 if not list:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000537 emit(NO_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000538 elif len(list) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000539 emit(ONE_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000540 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000541 emit(SOME_RECENT, period=period, count=len(list))
Guido van Rossum1f047721997-05-26 16:02:00 +0000542 self.format_all(map(lambda (mtime, file): file, list), headers=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000543 emit(TAIL_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000544
545 def do_roulette(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000546 self.prologue(T_ROULETTE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000547 file = self.dir.roulette()
548 self.dir.show(file)
549
550 def do_help(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000551 self.prologue(T_HELP)
552 emit(HELP)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000553
554 def do_show(self):
555 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000556 self.prologue(T_SHOW)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000557 entry.show()
558
559 def do_add(self):
560 self.prologue(T_ADD)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000561 emit(ADD_HEAD)
562 sections = SECTION_TITLES.items()
563 sections.sort()
564 for section, title in sections:
565 emit(ADD_SECTION, section=section, title=title)
566 emit(ADD_TAIL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000567
568 def do_delete(self):
569 self.prologue(T_DELETE)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000570 emit(DELETE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000571
572 def do_log(self):
573 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000574 self.prologue(T_LOG, entry)
575 emit(LOG, entry)
576 self.rlog(interpolate(SH_RLOG, entry), entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000577
578 def rlog(self, command, entry=None):
579 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000580 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000581 athead = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000582 lines = string.split(output, '\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000583 while lines and not lines[-1]:
584 del lines[-1]
585 if lines:
586 line = lines[-1]
587 if line[:1] == '=' and len(line) >= 40 and \
588 line == line[0]*len(line):
589 del lines[-1]
590 for line in lines:
591 if entry and athead and line[:9] == 'revision ':
592 rev = string.strip(line[9:])
Guido van Rossumea31ea21997-05-26 05:43:29 +0000593 if rev != '1.1':
594 emit(DIFFLINK, entry, rev=rev, line=line)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000595 else:
596 print line
597 athead = 0
598 else:
599 athead = 0
600 if line[:1] == '-' and len(line) >= 20 and \
601 line == len(line) * line[0]:
602 athead = 1
Guido van Rossumea31ea21997-05-26 05:43:29 +0000603 sys.stdout.write('<HR>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000604 else:
605 print line
Guido van Rossumea31ea21997-05-26 05:43:29 +0000606 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000607
608 def do_diff(self):
609 entry = self.dir.open(self.ui.file)
610 rev = self.ui.rev
611 r = regex.compile(
Guido van Rossumea31ea21997-05-26 05:43:29 +0000612 '^\([1-9][0-9]?[0-9]?\)\.\([1-9][0-9]?[0-9]?[0-9]?\)$')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000613 if r.match(rev) < 0:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000614 self.error("Invalid revision number: %s." % `rev`)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000615 [major, minor] = map(string.atoi, r.group(1, 2))
616 if minor == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000617 self.error("No previous revision.")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000618 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000619 prev = '%d.%d' % (major, minor-1)
620 self.prologue(T_DIFF, entry)
621 self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000622
623 def shell(self, command):
624 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000625 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000626 print escape(output)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000627 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000628
629 def do_new(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000630 entry = self.dir.new(section=string.atoi(self.ui.section))
631 entry.version = '*new*'
632 self.prologue(T_EDIT)
633 emit(EDITHEAD)
634 emit(EDITFORM1, entry, editversion=entry.version)
635 emit(EDITFORM2, entry, load_my_cookie())
636 emit(EDITFORM3)
637 entry.show(edit=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000638
639 def do_edit(self):
640 entry = self.dir.open(self.ui.file)
641 entry.load_version()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000642 self.prologue(T_EDIT)
643 emit(EDITHEAD)
644 emit(EDITFORM1, entry, editversion=entry.version)
645 emit(EDITFORM2, entry, load_my_cookie())
646 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000647 entry.show(edit=0)
648
649 def do_review(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000650 send_my_cookie(self.ui)
651 if self.ui.editversion == '*new*':
652 sec, num = self.dir.parse(self.ui.file)
653 entry = self.dir.new(section=sec)
654 entry.version = "*new*"
655 if entry.file != self.ui.file:
656 self.error("Commit version conflict!")
657 emit(NEWCONFLICT, self.ui, sec=sec, num=num)
658 return
659 else:
660 entry = self.dir.open(self.ui.file)
661 entry.load_version()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000662 # Check that the FAQ entry number didn't change
663 if string.split(self.ui.title)[:1] != string.split(entry.title)[:1]:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000664 self.error("Don't change the entry number please!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000665 return
666 # Check that the edited version is the current version
667 if entry.version != self.ui.editversion:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000668 self.error("Commit version conflict!")
669 emit(VERSIONCONFLICT, entry, self.ui)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000670 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000671 commit_ok = ((not PASSWORD
672 or self.ui.password == PASSWORD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000673 and self.ui.author
674 and '@' in self.ui.email
675 and self.ui.log)
676 if self.ui.commit:
677 if not commit_ok:
678 self.cantcommit()
679 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000680 self.commit(entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000681 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000682 self.prologue(T_REVIEW)
683 emit(REVIEWHEAD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000684 entry.body = self.ui.body
685 entry.title = self.ui.title
686 entry.show(edit=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000687 emit(EDITFORM1, self.ui, entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000688 if commit_ok:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000689 emit(COMMIT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000690 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000691 emit(NOCOMMIT)
692 emit(EDITFORM2, self.ui, entry, load_my_cookie())
693 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000694
695 def cantcommit(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000696 self.prologue(T_CANTCOMMIT)
697 print CANTCOMMIT_HEAD
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000698 if not self.ui.passwd:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000699 emit(NEED_PASSWD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000700 if not self.ui.log:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000701 emit(NEED_LOG)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000702 if not self.ui.author:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000703 emit(NEED_AUTHOR)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000704 if not self.ui.email:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000705 emit(NEED_EMAIL)
706 print CANTCOMMIT_TAIL
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000707
Guido van Rossumea31ea21997-05-26 05:43:29 +0000708 def commit(self, entry):
709 file = entry.file
710 # Normalize line endings in body
711 if '\r' in self.ui.body:
712 import regsub
713 self.ui.body = regsub.gsub('\r\n?', '\n', self.ui.body)
714 # Normalize whitespace in title
715 self.ui.title = string.join(string.split(self.ui.title))
716 # Check that there were any changes
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000717 if self.ui.body == entry.body and self.ui.title == entry.title:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000718 self.error("You didn't make any changes!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000719 return
720 # XXX Should lock here
721 try:
722 os.unlink(file)
723 except os.error:
724 pass
725 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000726 f = open(file, 'w')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000727 except IOError, why:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000728 self.error(CANTWRITE, file=file, why=why)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000729 return
730 date = time.ctime(time.time())
Guido van Rossumea31ea21997-05-26 05:43:29 +0000731 emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
732 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000733 f.write(self.ui.body)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000734 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000735 f.close()
736
737 import tempfile
738 tfn = tempfile.mktemp()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000739 f = open(tfn, 'w')
740 emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000741 f.close()
742
743 command = interpolate(
Guido van Rossumea31ea21997-05-26 05:43:29 +0000744 SH_LOCK + '\n' + SH_CHECKIN,
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000745 file=file, tfn=tfn)
746
747 p = os.popen(command)
748 output = p.read()
749 sts = p.close()
750 # XXX Should unlock here
751 if not sts:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000752 self.prologue(T_COMMITTED)
753 emit(COMMITTED)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000754 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000755 self.error(T_COMMITFAILED)
756 emit(COMMITFAILED, sts=sts)
757 print '<PRE>%s</PRE>' % escape(output)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000758
759 try:
760 os.unlink(tfn)
761 except os.error:
762 pass
763
764 entry = self.dir.open(file)
765 entry.show()
766
767wiz = FaqWizard()
768wiz.go()
769
Guido van Rossumea31ea21997-05-26 05:43:29 +0000770# This bootstrap script should be placed in your cgi-bin directory.
771# You only need to edit the first two lines: change
772# /usr/local/bin/python to where your Python interpreter lives change
773# the value for FAQDIR to where your FAQ lives. The faqwiz.py and
774# faqconf.py files should live there, too.
775
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000776BOOTSTRAP = """\
777#! /usr/local/bin/python
778FAQDIR = "/usr/people/guido/python/FAQ"
Guido van Rossumea31ea21997-05-26 05:43:29 +0000779import sys, os
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000780os.chdir(FAQDIR)
781sys.path.insert(0, FAQDIR)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000782import faqwiz
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000783"""