blob: 5ceb2f93085e18e160fb5796de8073cc918a0677 [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
73def translate(text):
74 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)
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)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000161 then = now + COOKIE_LIFETIME
162 gmt = time.gmtime(then)
163 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
164 print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000165
Guido van Rossumea31ea21997-05-26 05:43:29 +0000166class MagicDict:
167
168 def __init__(self, d, quote):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000169 self.__d = d
Guido van Rossumea31ea21997-05-26 05:43:29 +0000170 self.__quote = quote
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000171
172 def __getitem__(self, key):
173 for d in self.__d:
174 try:
175 value = d[key]
176 if value:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000177 value = str(value)
178 if self.__quote:
179 value = escapeq(value)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000180 return value
181 except KeyError:
182 pass
Guido van Rossumea31ea21997-05-26 05:43:29 +0000183 return ''
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000184
185class UserInput:
186
187 def __init__(self):
188 self.__form = cgi.FieldStorage()
189
190 def __getattr__(self, name):
191 if name[0] == '_':
192 raise AttributeError
193 try:
194 value = self.__form[name].value
195 except (TypeError, KeyError):
196 value = ''
197 else:
198 value = string.strip(value)
199 setattr(self, name, value)
200 return value
201
202 def __getitem__(self, key):
203 return getattr(self, key)
204
Guido van Rossumea31ea21997-05-26 05:43:29 +0000205class FaqEntry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000206
Guido van Rossumea31ea21997-05-26 05:43:29 +0000207 def __init__(self, fp, file, sec_num):
208 self.file = file
209 self.sec, self.num = sec_num
210 if fp:
211 import rfc822
212 self.__headers = rfc822.Message(fp)
213 self.body = string.strip(fp.read())
214 else:
215 self.__headers = {'title': "%d.%d. " % sec_num}
216 self.body = ''
217
218 def __getattr__(self, name):
219 if name[0] == '_':
220 raise AttributeError
221 key = string.join(string.split(name, '_'), '-')
222 try:
223 value = self.__headers[key]
224 except KeyError:
225 value = ''
226 setattr(self, name, value)
227 return value
228
229 def __getitem__(self, key):
230 return getattr(self, key)
231
232 def load_version(self):
233 command = interpolate(SH_RLOG_H, self)
234 p = os.popen(command)
235 version = ''
236 while 1:
237 line = p.readline()
238 if not line:
239 break
240 if line[:5] == 'head:':
241 version = string.strip(line[5:])
242 p.close()
243 self.version = version
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000244
Guido van Rossum7a241071997-05-26 19:46:56 +0000245 def getmtime(self):
246 if not self.last_changed_date:
247 return 0
248 try:
249 return os.stat(self.file)[stat.ST_MTIME]
250 except os.error:
251 return 0
252
253 def emit_marks(self):
254 mtime = self.getmtime()
255 if mtime >= now - DT_VERY_RECENT:
256 emit(MARK_VERY_RECENT, self)
257 elif mtime >= now - DT_RECENT:
258 emit(MARK_RECENT, self)
259
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000260 def show(self, edit=1):
Guido van Rossum7a241071997-05-26 19:46:56 +0000261 emit(ENTRY_HEADER1, self)
262 self.emit_marks()
263 emit(ENTRY_HEADER2, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000264 pre = 0
Guido van Rossumf1ead1a1997-08-28 02:38:01 +0000265 raw = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000266 for line in string.split(self.body, '\n'):
Guido van Rossumf1ead1a1997-08-28 02:38:01 +0000267 # Allow the user to insert raw html into a FAQ answer
268 # (Skip Montanaro, with changes by Guido)
269 tag = string.lower(string.rstrip(line))
270 if tag == '<html>':
271 raw = 1
272 continue
273 if tag == '</html>':
274 raw = 0
275 continue
276 if raw:
277 print line
278 continue
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000279 if not string.strip(line):
280 if pre:
281 print '</PRE>'
282 pre = 0
283 else:
284 print '<P>'
285 else:
286 if line[0] not in string.whitespace:
287 if pre:
288 print '</PRE>'
289 pre = 0
290 else:
291 if not pre:
292 print '<PRE>'
293 pre = 1
294 if '/' in line or '@' in line:
295 line = translate(line)
296 elif '<' in line or '&' in line:
297 line = escape(line)
298 if not pre and '*' in line:
299 line = emphasize(line)
300 print line
301 if pre:
302 print '</PRE>'
303 pre = 0
304 if edit:
305 print '<P>'
Guido van Rossumea31ea21997-05-26 05:43:29 +0000306 emit(ENTRY_FOOTER, self)
307 if self.last_changed_date:
308 emit(ENTRY_LOGINFO, self)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000309 print '<P>'
310
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000311class FaqDir:
312
313 entryclass = FaqEntry
314
Guido van Rossumea31ea21997-05-26 05:43:29 +0000315 __okprog = regex.compile(OKFILENAME)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000316
317 def __init__(self, dir=os.curdir):
318 self.__dir = dir
319 self.__files = None
320
321 def __fill(self):
322 if self.__files is not None:
323 return
324 self.__files = files = []
325 okprog = self.__okprog
326 for file in os.listdir(self.__dir):
327 if okprog.match(file) >= 0:
328 files.append(file)
329 files.sort()
330
331 def good(self, file):
332 return self.__okprog.match(file) >= 0
333
334 def parse(self, file):
335 if not self.good(file):
336 return None
337 sec, num = self.__okprog.group(1, 2)
338 return string.atoi(sec), string.atoi(num)
339
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000340 def list(self):
341 # XXX Caller shouldn't modify result
342 self.__fill()
343 return self.__files
344
345 def open(self, file):
346 sec_num = self.parse(file)
347 if not sec_num:
348 raise InvalidFile(file)
349 try:
350 fp = open(file)
351 except IOError, msg:
352 raise NoSuchFile(file, msg)
353 try:
354 return self.entryclass(fp, file, sec_num)
355 finally:
356 fp.close()
357
358 def show(self, file, edit=1):
359 self.open(file).show(edit=edit)
360
Guido van Rossumea31ea21997-05-26 05:43:29 +0000361 def new(self, section):
362 if not SECTION_TITLES.has_key(section):
363 raise NoSuchSection(section)
364 maxnum = 0
365 for file in self.list():
366 sec, num = self.parse(file)
367 if sec == section:
368 maxnum = max(maxnum, num)
369 sec_num = (section, maxnum+1)
370 file = NEWFILENAME % sec_num
371 return self.entryclass(None, file, sec_num)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000372
373class FaqWizard:
374
375 def __init__(self):
376 self.ui = UserInput()
377 self.dir = FaqDir()
378
379 def go(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000380 print 'Content-type: text/html'
381 req = self.ui.req or 'home'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000382 mname = 'do_%s' % req
383 try:
384 meth = getattr(self, mname)
385 except AttributeError:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000386 self.error("Bad request type %s." % `req`)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000387 else:
388 try:
389 meth()
390 except InvalidFile, exc:
391 self.error("Invalid entry file name %s" % exc.file)
392 except NoSuchFile, exc:
393 self.error("No entry with file name %s" % exc.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000394 except NoSuchSection, exc:
395 self.error("No section number %s" % exc.section)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000396 self.epilogue()
397
398 def error(self, message, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000399 self.prologue(T_ERROR)
400 emit(message, kw)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000401
402 def prologue(self, title, entry=None, **kw):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000403 emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000404
405 def epilogue(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000406 emit(EPILOGUE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000407
408 def do_home(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000409 self.prologue(T_HOME)
410 emit(HOME)
411
412 def do_debug(self):
413 self.prologue("FAQ Wizard Debugging")
414 form = cgi.FieldStorage()
415 cgi.print_form(form)
416 cgi.print_environ(os.environ)
417 cgi.print_directory()
418 cgi.print_arguments()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000419
420 def do_search(self):
421 query = self.ui.query
422 if not query:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000423 self.error("Empty query string!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000424 return
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000425 if self.ui.querytype == 'simple':
Guido van Rossumea31ea21997-05-26 05:43:29 +0000426 for c in '\\.[]?+^$*':
427 if c in query:
428 query = replace(query, c, '\\'+c)
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000429 queries = [query]
430 elif self.ui.querytype in ('anykeywords', 'allkeywords'):
431 import regsub
432 words = string.split(regsub.gsub('[^a-zA-Z0-9]+', ' ', query))
433 if not words:
434 self.error("No keywords specified!")
435 return
436 words = map(lambda w: '\<%s\>' % w, words)
437 if self.ui.querytype[:3] == 'any':
438 queries = [string.join(words, '\|')]
439 else:
440 queries = words
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000441 else:
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000442 # Default to regex
443 queries = [query]
444 self.prologue(T_SEARCH)
445 progs = []
446 for query in queries:
447 if self.ui.casefold == 'no':
448 p = regex.compile(query)
449 else:
450 p = regex.compile(query, regex.casefold)
451 progs.append(p)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000452 hits = []
453 for file in self.dir.list():
454 try:
455 entry = self.dir.open(file)
456 except FileError:
457 constants
Guido van Rossum8cde0b41997-05-26 16:35:46 +0000458 for p in progs:
459 if p.search(entry.title) < 0 and p.search(entry.body) < 0:
460 break
461 else:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000462 hits.append(file)
463 if not hits:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000464 emit(NO_HITS, self.ui, count=0)
465 elif len(hits) <= MAXHITS:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000466 if len(hits) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000467 emit(ONE_HIT, count=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000468 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000469 emit(FEW_HITS, count=len(hits))
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000470 self.format_all(hits, headers=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000471 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000472 emit(MANY_HITS, count=len(hits))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000473 self.format_index(hits)
474
475 def do_all(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000476 self.prologue(T_ALL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000477 files = self.dir.list()
478 self.last_changed(files)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000479 self.format_index(files, localrefs=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000480 self.format_all(files)
481
482 def do_compat(self):
483 files = self.dir.list()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000484 emit(COMPAT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000485 self.last_changed(files)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000486 self.format_index(files, localrefs=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000487 self.format_all(files, edit=0)
Guido van Rossum7a241071997-05-26 19:46:56 +0000488 sys.exit(0) # XXX Hack to suppress epilogue
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000489
490 def last_changed(self, files):
491 latest = 0
492 for file in files:
Guido van Rossum7a241071997-05-26 19:46:56 +0000493 entry = self.dir.open(file)
494 if entry:
495 mtime = mtime = entry.getmtime()
496 if mtime > latest:
497 latest = mtime
Guido van Rossumc22eb011997-06-02 15:51:51 +0000498 print time.strftime(LAST_CHANGED, time.localtime(latest))
Guido van Rossum7a241071997-05-26 19:46:56 +0000499 emit(EXPLAIN_MARKS)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000500
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000501 def format_all(self, files, edit=1, headers=1):
502 sec = 0
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000503 for file in files:
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000504 try:
505 entry = self.dir.open(file)
506 except NoSuchFile:
507 continue
508 if headers and entry.sec != sec:
509 sec = entry.sec
510 try:
511 title = SECTION_TITLES[sec]
512 except KeyError:
513 title = "Untitled"
514 emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
515 sec=sec, title=title)
516 entry.show(edit=edit)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000517
518 def do_index(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000519 self.prologue(T_INDEX)
Guido van Rossum7a241071997-05-26 19:46:56 +0000520 files = self.dir.list()
521 self.last_changed(files)
522 self.format_index(files, add=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000523
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000524 def format_index(self, files, add=0, localrefs=0):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000525 sec = 0
526 for file in files:
527 try:
528 entry = self.dir.open(file)
529 except NoSuchFile:
530 continue
531 if entry.sec != sec:
532 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000533 if add:
534 emit(INDEX_ADDSECTION, sec=sec)
535 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000536 sec = entry.sec
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000537 try:
538 title = SECTION_TITLES[sec]
539 except KeyError:
540 title = "Untitled"
541 emit(INDEX_SECTION, sec=sec, title=title)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000542 if localrefs:
543 emit(LOCAL_ENTRY, entry)
544 else:
545 emit(INDEX_ENTRY, entry)
Guido van Rossum7a241071997-05-26 19:46:56 +0000546 entry.emit_marks()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000547 if sec:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000548 if add:
549 emit(INDEX_ADDSECTION, sec=sec)
550 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000551
552 def do_recent(self):
553 if not self.ui.days:
554 days = 1
555 else:
556 days = string.atof(self.ui.days)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000557 try:
558 cutoff = now - days * 24 * 3600
559 except OverflowError:
560 cutoff = 0
561 list = []
562 for file in self.dir.list():
Guido van Rossum7a241071997-05-26 19:46:56 +0000563 entry = self.dir.open(file)
564 if not entry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000565 continue
Guido van Rossum7a241071997-05-26 19:46:56 +0000566 mtime = entry.getmtime()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000567 if mtime >= cutoff:
568 list.append((mtime, file))
569 list.sort()
570 list.reverse()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000571 self.prologue(T_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000572 if days <= 1:
573 period = "%.2g hours" % (days*24)
574 else:
575 period = "%.6g days" % days
576 if not list:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000577 emit(NO_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000578 elif len(list) == 1:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000579 emit(ONE_RECENT, period=period)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000580 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000581 emit(SOME_RECENT, period=period, count=len(list))
Guido van Rossum1f047721997-05-26 16:02:00 +0000582 self.format_all(map(lambda (mtime, file): file, list), headers=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000583 emit(TAIL_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000584
585 def do_roulette(self):
Guido van Rossum525d52f1997-06-02 22:52:37 +0000586 import whrandom
587 files = self.dir.list()
588 if not files:
Guido van Rossum72a342f1997-05-30 11:58:21 +0000589 self.error("No entries.")
590 return
Guido van Rossum525d52f1997-06-02 22:52:37 +0000591 file = whrandom.choice(files)
Guido van Rossum72a342f1997-05-30 11:58:21 +0000592 self.prologue(T_ROULETTE)
Guido van Rossumf1ead1a1997-08-28 02:38:01 +0000593 emit(ROULETTE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000594 self.dir.show(file)
595
596 def do_help(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000597 self.prologue(T_HELP)
598 emit(HELP)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000599
600 def do_show(self):
601 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000602 self.prologue(T_SHOW)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000603 entry.show()
604
605 def do_add(self):
606 self.prologue(T_ADD)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000607 emit(ADD_HEAD)
608 sections = SECTION_TITLES.items()
609 sections.sort()
610 for section, title in sections:
611 emit(ADD_SECTION, section=section, title=title)
612 emit(ADD_TAIL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000613
614 def do_delete(self):
615 self.prologue(T_DELETE)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000616 emit(DELETE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000617
618 def do_log(self):
619 entry = self.dir.open(self.ui.file)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000620 self.prologue(T_LOG, entry)
621 emit(LOG, entry)
622 self.rlog(interpolate(SH_RLOG, entry), entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000623
624 def rlog(self, command, entry=None):
625 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000626 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000627 athead = 0
Guido van Rossumea31ea21997-05-26 05:43:29 +0000628 lines = string.split(output, '\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000629 while lines and not lines[-1]:
630 del lines[-1]
631 if lines:
632 line = lines[-1]
633 if line[:1] == '=' and len(line) >= 40 and \
634 line == line[0]*len(line):
635 del lines[-1]
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000636 headrev = None
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000637 for line in lines:
638 if entry and athead and line[:9] == 'revision ':
639 rev = string.strip(line[9:])
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000640 mami = revparse(rev)
641 if not mami:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000642 print line
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000643 else:
644 emit(REVISIONLINK, entry, rev=rev, line=line)
645 if mami[1] > 1:
646 prev = "%d.%d" % (mami[0], mami[1]-1)
647 emit(DIFFLINK, entry, prev=prev, rev=rev)
648 if headrev:
649 emit(DIFFLINK, entry, prev=rev, rev=headrev)
650 else:
651 headrev = rev
652 print
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000653 athead = 0
654 else:
655 athead = 0
656 if line[:1] == '-' and len(line) >= 20 and \
657 line == len(line) * line[0]:
658 athead = 1
Guido van Rossumea31ea21997-05-26 05:43:29 +0000659 sys.stdout.write('<HR>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000660 else:
661 print line
Guido van Rossumea31ea21997-05-26 05:43:29 +0000662 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000663
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000664 def do_revision(self):
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000665 entry = self.dir.open(self.ui.file)
666 rev = self.ui.rev
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000667 mami = revparse(rev)
668 if not mami:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000669 self.error("Invalid revision number: %s." % `rev`)
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000670 self.prologue(T_REVISION, entry)
671 self.shell(interpolate(SH_REVISION, entry, rev=rev))
672
673 def do_diff(self):
674 entry = self.dir.open(self.ui.file)
675 prev = self.ui.prev
676 rev = self.ui.rev
677 mami = revparse(rev)
678 if not mami:
679 self.error("Invalid revision number: %s." % `rev`)
680 if prev:
681 if not revparse(prev):
682 self.error("Invalid previous revision number: %s." % `prev`)
683 else:
684 prev = '%d.%d' % (mami[0], mami[1])
Guido van Rossumea31ea21997-05-26 05:43:29 +0000685 self.prologue(T_DIFF, entry)
686 self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000687
688 def shell(self, command):
689 output = os.popen(command).read()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000690 sys.stdout.write('<PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000691 print escape(output)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000692 print '</PRE>'
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000693
694 def do_new(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000695 entry = self.dir.new(section=string.atoi(self.ui.section))
696 entry.version = '*new*'
697 self.prologue(T_EDIT)
698 emit(EDITHEAD)
699 emit(EDITFORM1, entry, editversion=entry.version)
700 emit(EDITFORM2, entry, load_my_cookie())
701 emit(EDITFORM3)
702 entry.show(edit=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000703
704 def do_edit(self):
705 entry = self.dir.open(self.ui.file)
706 entry.load_version()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000707 self.prologue(T_EDIT)
708 emit(EDITHEAD)
709 emit(EDITFORM1, entry, editversion=entry.version)
710 emit(EDITFORM2, entry, load_my_cookie())
711 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000712 entry.show(edit=0)
713
714 def do_review(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000715 send_my_cookie(self.ui)
716 if self.ui.editversion == '*new*':
717 sec, num = self.dir.parse(self.ui.file)
718 entry = self.dir.new(section=sec)
719 entry.version = "*new*"
720 if entry.file != self.ui.file:
721 self.error("Commit version conflict!")
722 emit(NEWCONFLICT, self.ui, sec=sec, num=num)
723 return
724 else:
725 entry = self.dir.open(self.ui.file)
726 entry.load_version()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000727 # Check that the FAQ entry number didn't change
728 if string.split(self.ui.title)[:1] != string.split(entry.title)[:1]:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000729 self.error("Don't change the entry number please!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000730 return
731 # Check that the edited version is the current version
732 if entry.version != self.ui.editversion:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000733 self.error("Commit version conflict!")
734 emit(VERSIONCONFLICT, entry, self.ui)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000735 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000736 commit_ok = ((not PASSWORD
737 or self.ui.password == PASSWORD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000738 and self.ui.author
739 and '@' in self.ui.email
740 and self.ui.log)
741 if self.ui.commit:
742 if not commit_ok:
743 self.cantcommit()
744 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000745 self.commit(entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000746 return
Guido van Rossumea31ea21997-05-26 05:43:29 +0000747 self.prologue(T_REVIEW)
748 emit(REVIEWHEAD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000749 entry.body = self.ui.body
750 entry.title = self.ui.title
751 entry.show(edit=0)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000752 emit(EDITFORM1, self.ui, entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000753 if commit_ok:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000754 emit(COMMIT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000755 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000756 emit(NOCOMMIT)
757 emit(EDITFORM2, self.ui, entry, load_my_cookie())
758 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000759
760 def cantcommit(self):
Guido van Rossumea31ea21997-05-26 05:43:29 +0000761 self.prologue(T_CANTCOMMIT)
762 print CANTCOMMIT_HEAD
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000763 if not self.ui.passwd:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000764 emit(NEED_PASSWD)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000765 if not self.ui.log:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000766 emit(NEED_LOG)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000767 if not self.ui.author:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000768 emit(NEED_AUTHOR)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000769 if not self.ui.email:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000770 emit(NEED_EMAIL)
771 print CANTCOMMIT_TAIL
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000772
Guido van Rossumea31ea21997-05-26 05:43:29 +0000773 def commit(self, entry):
774 file = entry.file
775 # Normalize line endings in body
776 if '\r' in self.ui.body:
777 import regsub
778 self.ui.body = regsub.gsub('\r\n?', '\n', self.ui.body)
779 # Normalize whitespace in title
780 self.ui.title = string.join(string.split(self.ui.title))
781 # Check that there were any changes
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000782 if self.ui.body == entry.body and self.ui.title == entry.title:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000783 self.error("You didn't make any changes!")
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000784 return
785 # XXX Should lock here
786 try:
787 os.unlink(file)
788 except os.error:
789 pass
790 try:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000791 f = open(file, 'w')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000792 except IOError, why:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000793 self.error(CANTWRITE, file=file, why=why)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000794 return
Guido van Rossum7a241071997-05-26 19:46:56 +0000795 date = time.ctime(now)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000796 emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
797 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000798 f.write(self.ui.body)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000799 f.write('\n')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000800 f.close()
801
802 import tempfile
803 tfn = tempfile.mktemp()
Guido van Rossumea31ea21997-05-26 05:43:29 +0000804 f = open(tfn, 'w')
805 emit(LOGHEADER, self.ui, os.environ, date=date, _file=f)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000806 f.close()
807
808 command = interpolate(
Guido van Rossumea31ea21997-05-26 05:43:29 +0000809 SH_LOCK + '\n' + SH_CHECKIN,
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000810 file=file, tfn=tfn)
811
812 p = os.popen(command)
813 output = p.read()
814 sts = p.close()
815 # XXX Should unlock here
816 if not sts:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000817 self.prologue(T_COMMITTED)
818 emit(COMMITTED)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000819 else:
Guido van Rossumea31ea21997-05-26 05:43:29 +0000820 self.error(T_COMMITFAILED)
821 emit(COMMITFAILED, sts=sts)
822 print '<PRE>%s</PRE>' % escape(output)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000823
824 try:
825 os.unlink(tfn)
826 except os.error:
827 pass
828
829 entry = self.dir.open(file)
830 entry.show()
831
832wiz = FaqWizard()
833wiz.go()