blob: 9282b3614fd0f514355ff1e7363c6a8eb5d316df [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.
Guido van Rossumf1ead1a1997-08-28 02:38:01 +000010The actual script to place in cgi-bin is faqw.py.
Guido van Rossumea31ea21997-05-26 05:43:29 +000011
12"""
13
14import sys, string, time, os, stat, regex, cgi, faqconf
15from faqconf import * # This imports all uppercase names
Guido van Rossum7a241071997-05-26 19:46:56 +000016now = time.time()
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;')
Guido van Rossum1dccdc21997-08-14 20:17:20 +000044 s = replace(s, '>', '&gt;')
Guido van Rossumea31ea21997-05-26 05:43:29 +000045 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
Guido van Rossumb1823ad1997-12-09 16:04:46 +000073def translate(text, pre=0):
Guido van Rossum1677e5b1997-05-26 00:07:18 +000074 global translate_prog
75 if not translate_prog:
Guido van Rossum92c90321997-08-18 13:59:41 +000076 url = '\(http\|ftp\|https\)://[^ \t\r\n]*'
Guido van Rossum1677e5b1997-05-26 00:07:18 +000077 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)
Guido van Rossumb1823ad1997-12-09 16:04:46 +000093 if not pre or (pre and PROCESS_PREFORMAT):
94 if ':' in url:
95 repl = '<A HREF="%s">%s</A>' % (url, url)
96 else:
97 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
Guido van Rossum1677e5b1997-05-26 00:07:18 +000098 else:
Guido van Rossumb1823ad1997-12-09 16:04:46 +000099 repl = url
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000100 list.append(repl)
101 i = i + len(url)
102 j = len(text)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000103 list.append(escape(text[i:j]))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000104 return string.join(list, '')
105
106emphasize_prog = None
107
108def emphasize(line):
109 global emphasize_prog
110 import regsub
111 if not emphasize_prog:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000112 pat = '\*\([a-zA-Z]+\)\*'
113 emphasize_prog = regex.compile(pat)
114 return regsub.gsub(emphasize_prog, '<I>\\1</I>', line)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000115
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000116revparse_prog = None
117
118def revparse(rev):
119 global revparse_prog
120 if not revparse_prog:
121 revparse_prog = regex.compile(
122 '^\([1-9][0-9]?[0-9]?\)\.\([1-9][0-9]?[0-9]?[0-9]?\)$')
123 if revparse_prog.match(rev) < 0:
124 return None
125 [major, minor] = map(string.atoi, revparse_prog.group(1, 2))
126 return major, minor
127
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000128def load_cookies():
129 if not os.environ.has_key('HTTP_COOKIE'):
130 return {}
131 raw = os.environ['HTTP_COOKIE']
132 words = map(string.strip, string.split(raw, ';'))
133 cookies = {}
134 for word in words:
135 i = string.find(word, '=')
136 if i >= 0:
137 key, value = word[:i], word[i+1:]
138 cookies[key] = value
139 return cookies
140
141def load_my_cookie():
142 cookies = load_cookies()
143 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000144 value = cookies[COOKIE_NAME]
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000145 except KeyError:
146 return {}
147 import urllib
148 value = urllib.unquote(value)
149 words = string.split(value, '/')
150 while len(words) < 3:
151 words.append('')
152 author = string.join(words[:-2], '/')
153 email = words[-2]
154 password = words[-1]
155 return {'author': author,
156 'email': email,
157 'password': password}
158
Guido van Rossumea31ea21997-05-26 05:43:29 +0000159def send_my_cookie(ui):
160 name = COOKIE_NAME
161 value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
162 import urllib
163 value = urllib.quote(value)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000164 then = now + COOKIE_LIFETIME
165 gmt = time.gmtime(then)
166 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
167 print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000168
Guido van Rossumea31ea21997-05-26 05:43:29 +0000169class MagicDict:
170
171 def __init__(self, d, quote):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000172 self.__d = d
Guido van Rossumea31ea21997-05-26 05:43:29 +0000173 self.__quote = quote
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000174
175 def __getitem__(self, key):
176 for d in self.__d:
177 try:
178 value = d[key]
179 if value:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000180 value = str(value)
181 if self.__quote:
182 value = escapeq(value)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000183 return value
184 except KeyError:
185 pass
Guido van Rossumea31ea21997-05-26 05:43:29 +0000186 return ''
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000187
188class UserInput:
189
190 def __init__(self):
191 self.__form = cgi.FieldStorage()
192
193 def __getattr__(self, name):
194 if name[0] == '_':
195 raise AttributeError
196 try:
197 value = self.__form[name].value
198 except (TypeError, KeyError):
199 value = ''
200 else:
201 value = string.strip(value)
202 setattr(self, name, value)
203 return value
204
205 def __getitem__(self, key):
206 return getattr(self, key)
207
Guido van Rossumea31ea21997-05-26 05:43:29 +0000208class FaqEntry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000209
Guido van Rossumea31ea21997-05-26 05:43:29 +0000210 def __init__(self, fp, file, sec_num):
211 self.file = file
212 self.sec, self.num = sec_num
213 if fp:
214 import rfc822
215 self.__headers = rfc822.Message(fp)
216 self.body = string.strip(fp.read())
217 else:
218 self.__headers = {'title': "%d.%d. " % sec_num}
219 self.body = ''
220
221 def __getattr__(self, name):
222 if name[0] == '_':
223 raise AttributeError
224 key = string.join(string.split(name, '_'), '-')
225 try:
226 value = self.__headers[key]
227 except KeyError:
228 value = ''
229 setattr(self, name, value)
230 return value
231
232 def __getitem__(self, key):
233 return getattr(self, key)
234
235 def load_version(self):
236 command = interpolate(SH_RLOG_H, self)
237 p = os.popen(command)
238 version = ''
239 while 1:
240 line = p.readline()
241 if not line:
242 break
243 if line[:5] == 'head:':
244 version = string.strip(line[5:])
245 p.close()
246 self.version = version
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000247
Guido van Rossum7a241071997-05-26 19:46:56 +0000248 def getmtime(self):
249 if not self.last_changed_date:
250 return 0
251 try:
252 return os.stat(self.file)[stat.ST_MTIME]
253 except os.error:
254 return 0
255
256 def emit_marks(self):
257 mtime = self.getmtime()
258 if mtime >= now - DT_VERY_RECENT:
259 emit(MARK_VERY_RECENT, self)
260 elif mtime >= now - DT_RECENT:
261 emit(MARK_RECENT, self)
262
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000263 def show(self, edit=1):
Guido van Rossum7a241071997-05-26 19:46:56 +0000264 emit(ENTRY_HEADER1, self)
265 self.emit_marks()
266 emit(ENTRY_HEADER2, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000267 pre = 0
Guido van Rossumf1ead1a1997-08-28 02:38:01 +0000268 raw = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000269 for line in string.split(self.body, '\n'):
Guido van Rossumf1ead1a1997-08-28 02:38:01 +0000270 # Allow the user to insert raw html into a FAQ answer
271 # (Skip Montanaro, with changes by Guido)
272 tag = string.lower(string.rstrip(line))
273 if tag == '<html>':
274 raw = 1
275 continue
276 if tag == '</html>':
277 raw = 0
278 continue
279 if raw:
280 print line
281 continue
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000282 if not string.strip(line):
283 if pre:
284 print '</PRE>'
285 pre = 0
286 else:
287 print '<P>'
288 else:
289 if line[0] not in string.whitespace:
290 if pre:
291 print '</PRE>'
292 pre = 0
293 else:
294 if not pre:
295 print '<PRE>'
296 pre = 1
297 if '/' in line or '@' in line:
Guido van Rossumb1823ad1997-12-09 16:04:46 +0000298 line = translate(line, pre)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000299 elif '<' in line or '&' in line:
300 line = escape(line)
301 if not pre and '*' in line:
302 line = emphasize(line)
303 print line
304 if pre:
305 print '</PRE>'
306 pre = 0
307 if edit:
308 print '<P>'
Guido van Rossumea31ea21997-05-26 05:43:29 +0000309 emit(ENTRY_FOOTER, self)
310 if self.last_changed_date:
311 emit(ENTRY_LOGINFO, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000312 print '<P>'
313
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000314class FaqDir:
315
316 entryclass = FaqEntry
317
Guido van Rossumea31ea21997-05-26 05:43:29 +0000318 __okprog = regex.compile(OKFILENAME)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000319
320 def __init__(self, dir=os.curdir):
321 self.__dir = dir
322 self.__files = None
323
324 def __fill(self):
325 if self.__files is not None:
326 return
327 self.__files = files = []
328 okprog = self.__okprog
329 for file in os.listdir(self.__dir):
330 if okprog.match(file) >= 0:
331 files.append(file)
332 files.sort()
333
334 def good(self, file):
335 return self.__okprog.match(file) >= 0
336
337 def parse(self, file):
338 if not self.good(file):
339 return None
340 sec, num = self.__okprog.group(1, 2)
341 return string.atoi(sec), string.atoi(num)
342
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000343 def list(self):
344 # XXX Caller shouldn't modify result
345 self.__fill()
346 return self.__files
347
348 def open(self, file):
349 sec_num = self.parse(file)
350 if not sec_num:
351 raise InvalidFile(file)
352 try:
353 fp = open(file)
354 except IOError, msg:
355 raise NoSuchFile(file, msg)
356 try:
357 return self.entryclass(fp, file, sec_num)
358 finally:
359 fp.close()
360
361 def show(self, file, edit=1):
362 self.open(file).show(edit=edit)
363
Guido van Rossumea31ea21997-05-26 05:43:29 +0000364 def new(self, section):
365 if not SECTION_TITLES.has_key(section):
366 raise NoSuchSection(section)
367 maxnum = 0
368 for file in self.list():
369 sec, num = self.parse(file)
370 if sec == section:
371 maxnum = max(maxnum, num)
372 sec_num = (section, maxnum+1)
373 file = NEWFILENAME % sec_num
374 return self.entryclass(None, file, sec_num)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000375
376class FaqWizard:
377
378 def __init__(self):
379 self.ui = UserInput()
380 self.dir = FaqDir()
381
382 def go(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000383 print 'Content-type: text/html'
384 req = self.ui.req or 'home'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000385 mname = 'do_%s' % req
386 try:
387 meth = getattr(self, mname)
388 except AttributeError:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000389 self.error("Bad request type %s." % `req`)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000390 else:
391 try:
392 meth()
393 except InvalidFile, exc:
394 self.error("Invalid entry file name %s" % exc.file)
395 except NoSuchFile, exc:
396 self.error("No entry with file name %s" % exc.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000397 except NoSuchSection, exc:
398 self.error("No section number %s" % exc.section)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000399 self.epilogue()
400
401 def error(self, message, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000402 self.prologue(T_ERROR)
403 emit(message, kw)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000404
405 def prologue(self, title, entry=None, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000406 emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000407
408 def epilogue(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000409 emit(EPILOGUE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000410
411 def do_home(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000412 self.prologue(T_HOME)
413 emit(HOME)
414
415 def do_debug(self):
416 self.prologue("FAQ Wizard Debugging")
417 form = cgi.FieldStorage()
418 cgi.print_form(form)
419 cgi.print_environ(os.environ)
420 cgi.print_directory()
421 cgi.print_arguments()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000422
423 def do_search(self):
424 query = self.ui.query
425 if not query:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000426 self.error("Empty query string!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000427 return
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000428 if self.ui.querytype == 'simple':
Guido van Rossumea31ea21997-05-26 05:43:29 +0000429 for c in '\\.[]?+^$*':
430 if c in query:
431 query = replace(query, c, '\\'+c)
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000432 queries = [query]
433 elif self.ui.querytype in ('anykeywords', 'allkeywords'):
434 import regsub
435 words = string.split(regsub.gsub('[^a-zA-Z0-9]+', ' ', query))
436 if not words:
437 self.error("No keywords specified!")
438 return
439 words = map(lambda w: '\<%s\>' % w, words)
440 if self.ui.querytype[:3] == 'any':
441 queries = [string.join(words, '\|')]
442 else:
443 queries = words
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000444 else:
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000445 # Default to regex
446 queries = [query]
447 self.prologue(T_SEARCH)
448 progs = []
449 for query in queries:
450 if self.ui.casefold == 'no':
451 p = regex.compile(query)
452 else:
453 p = regex.compile(query, regex.casefold)
454 progs.append(p)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000455 hits = []
456 for file in self.dir.list():
457 try:
458 entry = self.dir.open(file)
459 except FileError:
460 constants
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000461 for p in progs:
462 if p.search(entry.title) < 0 and p.search(entry.body) < 0:
463 break
464 else:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000465 hits.append(file)
466 if not hits:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000467 emit(NO_HITS, self.ui, count=0)
468 elif len(hits) <= MAXHITS:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000469 if len(hits) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000470 emit(ONE_HIT, count=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000471 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000472 emit(FEW_HITS, count=len(hits))
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000473 self.format_all(hits, headers=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000474 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000475 emit(MANY_HITS, count=len(hits))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000476 self.format_index(hits)
477
478 def do_all(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000479 self.prologue(T_ALL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000480 files = self.dir.list()
481 self.last_changed(files)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000482 self.format_index(files, localrefs=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000483 self.format_all(files)
484
485 def do_compat(self):
486 files = self.dir.list()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000487 emit(COMPAT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000488 self.last_changed(files)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000489 self.format_index(files, localrefs=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000490 self.format_all(files, edit=0)
Guido van Rossum7a241071997-05-26 19:46:56 +0000491 sys.exit(0) # XXX Hack to suppress epilogue
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000492
493 def last_changed(self, files):
494 latest = 0
495 for file in files:
Guido van Rossum7a241071997-05-26 19:46:56 +0000496 entry = self.dir.open(file)
497 if entry:
498 mtime = mtime = entry.getmtime()
499 if mtime > latest:
500 latest = mtime
Guido van Rossumc22eb011997-06-02 15:51:51 +0000501 print time.strftime(LAST_CHANGED, time.localtime(latest))
Guido van Rossum7a241071997-05-26 19:46:56 +0000502 emit(EXPLAIN_MARKS)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000503
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000504 def format_all(self, files, edit=1, headers=1):
505 sec = 0
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000506 for file in files:
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000507 try:
508 entry = self.dir.open(file)
509 except NoSuchFile:
510 continue
511 if headers and entry.sec != sec:
512 sec = entry.sec
513 try:
514 title = SECTION_TITLES[sec]
515 except KeyError:
516 title = "Untitled"
517 emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
518 sec=sec, title=title)
519 entry.show(edit=edit)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000520
521 def do_index(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000522 self.prologue(T_INDEX)
Guido van Rossum7a241071997-05-26 19:46:56 +0000523 files = self.dir.list()
524 self.last_changed(files)
525 self.format_index(files, add=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000526
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000527 def format_index(self, files, add=0, localrefs=0):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000528 sec = 0
529 for file in files:
530 try:
531 entry = self.dir.open(file)
532 except NoSuchFile:
533 continue
534 if entry.sec != sec:
535 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000536 if add:
537 emit(INDEX_ADDSECTION, sec=sec)
538 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000539 sec = entry.sec
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000540 try:
541 title = SECTION_TITLES[sec]
542 except KeyError:
543 title = "Untitled"
544 emit(INDEX_SECTION, sec=sec, title=title)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000545 if localrefs:
546 emit(LOCAL_ENTRY, entry)
547 else:
548 emit(INDEX_ENTRY, entry)
Guido van Rossum7a241071997-05-26 19:46:56 +0000549 entry.emit_marks()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000550 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000551 if add:
552 emit(INDEX_ADDSECTION, sec=sec)
553 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000554
555 def do_recent(self):
556 if not self.ui.days:
557 days = 1
558 else:
559 days = string.atof(self.ui.days)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000560 try:
561 cutoff = now - days * 24 * 3600
562 except OverflowError:
563 cutoff = 0
564 list = []
565 for file in self.dir.list():
Guido van Rossum7a241071997-05-26 19:46:56 +0000566 entry = self.dir.open(file)
567 if not entry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000568 continue
Guido van Rossum7a241071997-05-26 19:46:56 +0000569 mtime = entry.getmtime()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000570 if mtime >= cutoff:
571 list.append((mtime, file))
572 list.sort()
573 list.reverse()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000574 self.prologue(T_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000575 if days <= 1:
576 period = "%.2g hours" % (days*24)
577 else:
578 period = "%.6g days" % days
579 if not list:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000580 emit(NO_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000581 elif len(list) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000582 emit(ONE_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000583 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000584 emit(SOME_RECENT, period=period, count=len(list))
Guido van Rossum1f047721997-05-26 16:02:00 +0000585 self.format_all(map(lambda (mtime, file): file, list), headers=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000586 emit(TAIL_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000587
588 def do_roulette(self):
Guido van Rossum525d52f1997-06-02 22:52:37 +0000589 import whrandom
590 files = self.dir.list()
591 if not files:
Guido van Rossum72a342f1997-05-30 11:58:21 +0000592 self.error("No entries.")
593 return
Guido van Rossum525d52f1997-06-02 22:52:37 +0000594 file = whrandom.choice(files)
Guido van Rossum72a342f1997-05-30 11:58:21 +0000595 self.prologue(T_ROULETTE)
Guido van Rossumf1ead1a1997-08-28 02:38:01 +0000596 emit(ROULETTE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000597 self.dir.show(file)
598
599 def do_help(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000600 self.prologue(T_HELP)
601 emit(HELP)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000602
603 def do_show(self):
604 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000605 self.prologue(T_SHOW)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000606 entry.show()
607
608 def do_add(self):
609 self.prologue(T_ADD)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000610 emit(ADD_HEAD)
611 sections = SECTION_TITLES.items()
612 sections.sort()
613 for section, title in sections:
614 emit(ADD_SECTION, section=section, title=title)
615 emit(ADD_TAIL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000616
617 def do_delete(self):
618 self.prologue(T_DELETE)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000619 emit(DELETE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000620
621 def do_log(self):
622 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000623 self.prologue(T_LOG, entry)
624 emit(LOG, entry)
625 self.rlog(interpolate(SH_RLOG, entry), entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000626
627 def rlog(self, command, entry=None):
628 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000629 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000630 athead = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000631 lines = string.split(output, '\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000632 while lines and not lines[-1]:
633 del lines[-1]
634 if lines:
635 line = lines[-1]
636 if line[:1] == '=' and len(line) >= 40 and \
637 line == line[0]*len(line):
638 del lines[-1]
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000639 headrev = None
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000640 for line in lines:
641 if entry and athead and line[:9] == 'revision ':
642 rev = string.strip(line[9:])
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000643 mami = revparse(rev)
644 if not mami:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000645 print line
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000646 else:
647 emit(REVISIONLINK, entry, rev=rev, line=line)
648 if mami[1] > 1:
649 prev = "%d.%d" % (mami[0], mami[1]-1)
650 emit(DIFFLINK, entry, prev=prev, rev=rev)
651 if headrev:
652 emit(DIFFLINK, entry, prev=rev, rev=headrev)
653 else:
654 headrev = rev
655 print
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000656 athead = 0
657 else:
658 athead = 0
659 if line[:1] == '-' and len(line) >= 20 and \
660 line == len(line) * line[0]:
661 athead = 1
Guido van Rossumea31ea21997-05-26 05:43:29 +0000662 sys.stdout.write('<HR>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000663 else:
664 print line
Guido van Rossumea31ea21997-05-26 05:43:29 +0000665 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000666
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000667 def do_revision(self):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000668 entry = self.dir.open(self.ui.file)
669 rev = self.ui.rev
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000670 mami = revparse(rev)
671 if not mami:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000672 self.error("Invalid revision number: %s." % `rev`)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000673 self.prologue(T_REVISION, entry)
674 self.shell(interpolate(SH_REVISION, entry, rev=rev))
675
676 def do_diff(self):
677 entry = self.dir.open(self.ui.file)
678 prev = self.ui.prev
679 rev = self.ui.rev
680 mami = revparse(rev)
681 if not mami:
682 self.error("Invalid revision number: %s." % `rev`)
683 if prev:
684 if not revparse(prev):
685 self.error("Invalid previous revision number: %s." % `prev`)
686 else:
687 prev = '%d.%d' % (mami[0], mami[1])
Guido van Rossumea31ea21997-05-26 05:43:29 +0000688 self.prologue(T_DIFF, entry)
689 self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000690
691 def shell(self, command):
692 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000693 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000694 print escape(output)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000695 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000696
697 def do_new(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000698 entry = self.dir.new(section=string.atoi(self.ui.section))
699 entry.version = '*new*'
700 self.prologue(T_EDIT)
701 emit(EDITHEAD)
702 emit(EDITFORM1, entry, editversion=entry.version)
703 emit(EDITFORM2, entry, load_my_cookie())
704 emit(EDITFORM3)
705 entry.show(edit=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000706
707 def do_edit(self):
708 entry = self.dir.open(self.ui.file)
709 entry.load_version()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000710 self.prologue(T_EDIT)
711 emit(EDITHEAD)
712 emit(EDITFORM1, entry, editversion=entry.version)
713 emit(EDITFORM2, entry, load_my_cookie())
714 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000715 entry.show(edit=0)
716
717 def do_review(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000718 send_my_cookie(self.ui)
719 if self.ui.editversion == '*new*':
720 sec, num = self.dir.parse(self.ui.file)
721 entry = self.dir.new(section=sec)
722 entry.version = "*new*"
723 if entry.file != self.ui.file:
724 self.error("Commit version conflict!")
725 emit(NEWCONFLICT, self.ui, sec=sec, num=num)
726 return
727 else:
728 entry = self.dir.open(self.ui.file)
729 entry.load_version()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000730 # Check that the FAQ entry number didn't change
731 if string.split(self.ui.title)[:1] != string.split(entry.title)[:1]:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000732 self.error("Don't change the entry number please!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000733 return
734 # Check that the edited version is the current version
735 if entry.version != self.ui.editversion:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000736 self.error("Commit version conflict!")
737 emit(VERSIONCONFLICT, entry, self.ui)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000738 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000739 commit_ok = ((not PASSWORD
740 or self.ui.password == PASSWORD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000741 and self.ui.author
742 and '@' in self.ui.email
743 and self.ui.log)
744 if self.ui.commit:
745 if not commit_ok:
746 self.cantcommit()
747 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000748 self.commit(entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000749 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000750 self.prologue(T_REVIEW)
751 emit(REVIEWHEAD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000752 entry.body = self.ui.body
753 entry.title = self.ui.title
754 entry.show(edit=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000755 emit(EDITFORM1, self.ui, entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000756 if commit_ok:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000757 emit(COMMIT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000758 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000759 emit(NOCOMMIT)
760 emit(EDITFORM2, self.ui, entry, load_my_cookie())
761 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000762
763 def cantcommit(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000764 self.prologue(T_CANTCOMMIT)
765 print CANTCOMMIT_HEAD
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000766 if not self.ui.passwd:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000767 emit(NEED_PASSWD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000768 if not self.ui.log:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000769 emit(NEED_LOG)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000770 if not self.ui.author:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000771 emit(NEED_AUTHOR)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000772 if not self.ui.email:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000773 emit(NEED_EMAIL)
774 print CANTCOMMIT_TAIL
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000775
Guido van Rossumea31ea21997-05-26 05:43:29 +0000776 def commit(self, entry):
777 file = entry.file
778 # Normalize line endings in body
779 if '\r' in self.ui.body:
780 import regsub
781 self.ui.body = regsub.gsub('\r\n?', '\n', self.ui.body)
782 # Normalize whitespace in title
783 self.ui.title = string.join(string.split(self.ui.title))
784 # Check that there were any changes
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000785 if self.ui.body == entry.body and self.ui.title == entry.title:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000786 self.error("You didn't make any changes!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000787 return
788 # XXX Should lock here
789 try:
790 os.unlink(file)
791 except os.error:
792 pass
793 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000794 f = open(file, 'w')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000795 except IOError, why:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000796 self.error(CANTWRITE, file=file, why=why)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000797 return
Guido van Rossum7a241071997-05-26 19:46:56 +0000798 date = time.ctime(now)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000799 emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
800 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000801 f.write(self.ui.body)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000802 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000803 f.close()
804
805 import tempfile
806 tfn = tempfile.mktemp()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000807 f = open(tfn, 'w')
808 emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000809 f.close()
810
811 command = interpolate(
Guido van Rossumca2f69c1997-11-11 17:17:55 +0000812 SH_LOCK + '\n' + SH_CHECKIN,
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000813 file=file, tfn=tfn)
814
815 p = os.popen(command)
816 output = p.read()
817 sts = p.close()
818 # XXX Should unlock here
819 if not sts:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000820 self.prologue(T_COMMITTED)
821 emit(COMMITTED)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000822 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000823 self.error(T_COMMITFAILED)
824 emit(COMMITFAILED, sts=sts)
825 print '<PRE>%s</PRE>' % escape(output)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000826
827 try:
828 os.unlink(tfn)
829 except os.error:
830 pass
831
832 entry = self.dir.open(file)
833 entry.show()
834
835wiz = FaqWizard()
836wiz.go()