blob: b9ab65d3bf32063c205b7251f8ba08b457799bd2 [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
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000014import sys, time, os, stat, re, cgi, faqconf
Guido van Rossum72dc60c1998-04-06 14:24:36 +000015from 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):
Guido van Rossum72dc60c1998-04-06 14:24:36 +000020 self.file = file
Guido van Rossum1677e5b1997-05-26 00:07:18 +000021
22class InvalidFile(FileError):
23 pass
24
Guido van Rossumea31ea21997-05-26 05:43:29 +000025class NoSuchSection(FileError):
26 def __init__(self, section):
Guido van Rossum72dc60c1998-04-06 14:24:36 +000027 FileError.__init__(self, NEWFILENAME %(section, 1))
28 self.section = section
Guido van Rossumea31ea21997-05-26 05:43:29 +000029
Guido van Rossum1677e5b1997-05-26 00:07:18 +000030class NoSuchFile(FileError):
31 def __init__(self, file, why=None):
Guido van Rossum72dc60c1998-04-06 14:24:36 +000032 FileError.__init__(self, file)
33 self.why = why
Guido van Rossum1677e5b1997-05-26 00:07:18 +000034
Guido van Rossumea31ea21997-05-26 05:43:29 +000035def escape(s):
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000036 s = s.replace('&', '&')
37 s = s.replace('<', '&lt;')
38 s = s.replace('>', '&gt;')
Guido van Rossumea31ea21997-05-26 05:43:29 +000039 return s
40
Guido van Rossum1677e5b1997-05-26 00:07:18 +000041def escapeq(s):
42 s = escape(s)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000043 s = s.replace('"', '&quot;')
Guido van Rossum1677e5b1997-05-26 00:07:18 +000044 return s
45
Guido van Rossumea31ea21997-05-26 05:43:29 +000046def _interpolate(format, args, kw):
47 try:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000048 quote = kw['_quote']
Guido van Rossumea31ea21997-05-26 05:43:29 +000049 except KeyError:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000050 quote = 1
Guido van Rossumea31ea21997-05-26 05:43:29 +000051 d = (kw,) + args + (faqconf.__dict__,)
52 m = MagicDict(d, quote)
53 return format % m
Guido van Rossum1677e5b1997-05-26 00:07:18 +000054
Guido van Rossumea31ea21997-05-26 05:43:29 +000055def interpolate(format, *args, **kw):
56 return _interpolate(format, args, kw)
57
58def emit(format, *args, **kw):
59 try:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000060 f = kw['_file']
Guido van Rossumea31ea21997-05-26 05:43:29 +000061 except KeyError:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000062 f = sys.stdout
Guido van Rossumea31ea21997-05-26 05:43:29 +000063 f.write(_interpolate(format, args, kw))
Guido van Rossum1677e5b1997-05-26 00:07:18 +000064
65translate_prog = None
66
Guido van Rossumb1823ad1997-12-09 16:04:46 +000067def translate(text, pre=0):
Guido van Rossum1677e5b1997-05-26 00:07:18 +000068 global translate_prog
69 if not translate_prog:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000070 translate_prog = prog = re.compile(
71 r'\b(http|ftp|https)://\S+(\b|/)|\b[-.\w]+@[-.\w]+')
Guido van Rossum1677e5b1997-05-26 00:07:18 +000072 else:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000073 prog = translate_prog
Guido van Rossum1677e5b1997-05-26 00:07:18 +000074 i = 0
75 list = []
76 while 1:
Guido van Rossum72dc60c1998-04-06 14:24:36 +000077 m = prog.search(text, i)
78 if not m:
79 break
80 j = m.start()
81 list.append(escape(text[i:j]))
82 i = j
83 url = m.group(0)
84 while url[-1] in '();:,.?\'"<>':
85 url = url[:-1]
86 i = i + len(url)
87 url = escape(url)
88 if not pre or (pre and PROCESS_PREFORMAT):
89 if ':' in url:
90 repl = '<A HREF="%s">%s</A>' % (url, url)
91 else:
Guido van Rossum0922a561998-07-07 22:39:21 +000092 repl = '<A HREF="mailto:%s">%s</A>' % (url, url)
Guido van Rossum72dc60c1998-04-06 14:24:36 +000093 else:
94 repl = url
95 list.append(repl)
Guido van Rossum1677e5b1997-05-26 00:07:18 +000096 j = len(text)
Guido van Rossumea31ea21997-05-26 05:43:29 +000097 list.append(escape(text[i:j]))
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000098 return ''.join(list)
Guido van Rossum1677e5b1997-05-26 00:07:18 +000099
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000100def emphasize(line):
Guido van Rossum80e57fb1997-12-21 07:05:32 +0000101 return re.sub(r'\*([a-zA-Z]+)\*', r'<I>\1</I>', line)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000102
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000103revparse_prog = None
104
105def revparse(rev):
106 global revparse_prog
107 if not revparse_prog:
Guido van Rossum55b40b01998-05-22 19:43:21 +0000108 revparse_prog = re.compile(r'^(\d{1,3})\.(\d{1,4})$')
Guido van Rossum80e57fb1997-12-21 07:05:32 +0000109 m = revparse_prog.match(rev)
110 if not m:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000111 return None
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000112 [major, minor] = map(int, m.group(1, 2))
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000113 return major, minor
114
Guido van Rossum2b6004a2000-03-31 00:58:00 +0000115logon = 0
116def log(text):
117 if logon:
118 logfile = open("logfile", "a")
119 logfile.write(text + "\n")
120 logfile.close()
121
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000122def load_cookies():
Georg Brandlbf82e372008-05-16 17:02:34 +0000123 if 'HTTP_COOKIE' not in os.environ:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000124 return {}
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000125 raw = os.environ['HTTP_COOKIE']
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000126 words = [s.strip() for s in raw.split(';')]
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000127 cookies = {}
128 for word in words:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000129 i = word.find('=')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000130 if i >= 0:
131 key, value = word[:i], word[i+1:]
132 cookies[key] = value
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000133 return cookies
134
135def load_my_cookie():
136 cookies = load_cookies()
137 try:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000138 value = cookies[COOKIE_NAME]
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000139 except KeyError:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000140 return {}
Georg Brandl7d840552008-06-23 11:45:20 +0000141 import urllib.parse
142 value = urllib.parse.unquote(value)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000143 words = value.split('/')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000144 while len(words) < 3:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000145 words.append('')
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000146 author = '/'.join(words[:-2])
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000147 email = words[-2]
148 password = words[-1]
149 return {'author': author,
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000150 'email': email,
151 'password': password}
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000152
Guido van Rossumea31ea21997-05-26 05:43:29 +0000153def send_my_cookie(ui):
154 name = COOKIE_NAME
155 value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
Georg Brandl7d840552008-06-23 11:45:20 +0000156 import urllib.parse
157 value = urllib.parse.quote(value)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000158 then = now + COOKIE_LIFETIME
159 gmt = time.gmtime(then)
Guido van Rossum2053aa61998-09-04 21:19:55 +0000160 path = os.environ.get('SCRIPT_NAME', '/cgi-bin/')
Collin Winter6afaeb72007-08-03 17:06:41 +0000161 print("Set-Cookie: %s=%s; path=%s;" % (name, value, path), end=' ')
162 print(time.strftime("expires=%a, %d-%b-%y %X GMT", gmt))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000163
Guido van Rossumea31ea21997-05-26 05:43:29 +0000164class MagicDict:
165
166 def __init__(self, d, quote):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000167 self.__d = d
168 self.__quote = quote
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000169
170 def __getitem__(self, key):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000171 for d in self.__d:
172 try:
173 value = d[key]
174 if value:
175 value = str(value)
176 if self.__quote:
177 value = escapeq(value)
178 return value
179 except KeyError:
180 pass
181 return ''
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000182
183class UserInput:
184
185 def __init__(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000186 self.__form = cgi.FieldStorage()
Guido van Rossum2b6004a2000-03-31 00:58:00 +0000187 #log("\n\nbody: " + self.body)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000188
189 def __getattr__(self, name):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000190 if name[0] == '_':
191 raise AttributeError
192 try:
193 value = self.__form[name].value
194 except (TypeError, KeyError):
195 value = ''
196 else:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000197 value = value.strip()
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000198 setattr(self, name, value)
199 return value
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000200
201 def __getitem__(self, key):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000202 return getattr(self, key)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000203
Guido van Rossumea31ea21997-05-26 05:43:29 +0000204class FaqEntry:
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000205
Guido van Rossumea31ea21997-05-26 05:43:29 +0000206 def __init__(self, fp, file, sec_num):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000207 self.file = file
208 self.sec, self.num = sec_num
209 if fp:
Georg Brandl9f0f9602008-06-12 22:23:59 +0000210 import email
211 self.__headers = email.message_from_file(fp)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000212 self.body = fp.read().strip()
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000213 else:
214 self.__headers = {'title': "%d.%d. " % sec_num}
215 self.body = ''
Guido van Rossumea31ea21997-05-26 05:43:29 +0000216
217 def __getattr__(self, name):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000218 if name[0] == '_':
219 raise AttributeError
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000220 key = '-'.join(name.split('_'))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000221 try:
222 value = self.__headers[key]
223 except KeyError:
224 value = ''
225 setattr(self, name, value)
226 return value
Guido van Rossumea31ea21997-05-26 05:43:29 +0000227
228 def __getitem__(self, key):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000229 return getattr(self, key)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000230
231 def load_version(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000232 command = interpolate(SH_RLOG_H, self)
233 p = os.popen(command)
234 version = ''
235 while 1:
236 line = p.readline()
237 if not line:
238 break
239 if line[:5] == 'head:':
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000240 version = line[5:].strip()
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000241 p.close()
242 self.version = version
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000243
Guido van Rossum7a241071997-05-26 19:46:56 +0000244 def getmtime(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000245 if not self.last_changed_date:
246 return 0
247 try:
248 return os.stat(self.file)[stat.ST_MTIME]
249 except os.error:
250 return 0
Guido van Rossum7a241071997-05-26 19:46:56 +0000251
252 def emit_marks(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000253 mtime = self.getmtime()
254 if mtime >= now - DT_VERY_RECENT:
255 emit(MARK_VERY_RECENT, self)
256 elif mtime >= now - DT_RECENT:
257 emit(MARK_RECENT, self)
Guido van Rossum7a241071997-05-26 19:46:56 +0000258
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000259 def show(self, edit=1):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000260 emit(ENTRY_HEADER1, self)
261 self.emit_marks()
262 emit(ENTRY_HEADER2, self)
263 pre = 0
264 raw = 0
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000265 for line in self.body.split('\n'):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000266 # Allow the user to insert raw html into a FAQ answer
267 # (Skip Montanaro, with changes by Guido)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000268 tag = line.rstrip().lower()
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000269 if tag == '<html>':
270 raw = 1
271 continue
272 if tag == '</html>':
273 raw = 0
274 continue
275 if raw:
Collin Winter6afaeb72007-08-03 17:06:41 +0000276 print(line)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000277 continue
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000278 if not line.strip():
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000279 if pre:
Collin Winter6afaeb72007-08-03 17:06:41 +0000280 print('</PRE>')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000281 pre = 0
282 else:
Collin Winter6afaeb72007-08-03 17:06:41 +0000283 print('<P>')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000284 else:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000285 if not line[0].isspace():
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000286 if pre:
Collin Winter6afaeb72007-08-03 17:06:41 +0000287 print('</PRE>')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000288 pre = 0
289 else:
290 if not pre:
Collin Winter6afaeb72007-08-03 17:06:41 +0000291 print('<PRE>')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000292 pre = 1
293 if '/' in line or '@' in line:
294 line = translate(line, pre)
295 elif '<' in line or '&' in line:
296 line = escape(line)
297 if not pre and '*' in line:
298 line = emphasize(line)
Collin Winter6afaeb72007-08-03 17:06:41 +0000299 print(line)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000300 if pre:
Collin Winter6afaeb72007-08-03 17:06:41 +0000301 print('</PRE>')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000302 pre = 0
303 if edit:
Collin Winter6afaeb72007-08-03 17:06:41 +0000304 print('<P>')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000305 emit(ENTRY_FOOTER, self)
306 if self.last_changed_date:
307 emit(ENTRY_LOGINFO, self)
Collin Winter6afaeb72007-08-03 17:06:41 +0000308 print('<P>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000309
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000310class FaqDir:
311
312 entryclass = FaqEntry
313
Guido van Rossum80e57fb1997-12-21 07:05:32 +0000314 __okprog = re.compile(OKFILENAME)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000315
316 def __init__(self, dir=os.curdir):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000317 self.__dir = dir
318 self.__files = None
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000319
320 def __fill(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000321 if self.__files is not None:
322 return
323 self.__files = files = []
324 okprog = self.__okprog
325 for file in os.listdir(self.__dir):
326 if self.__okprog.match(file):
327 files.append(file)
328 files.sort()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000329
330 def good(self, file):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000331 return self.__okprog.match(file)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000332
333 def parse(self, file):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000334 m = self.good(file)
335 if not m:
336 return None
337 sec, num = m.group(1, 2)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000338 return int(sec), int(num)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000339
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000340 def list(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000341 # XXX Caller shouldn't modify result
342 self.__fill()
343 return self.__files
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000344
345 def open(self, file):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000346 sec_num = self.parse(file)
347 if not sec_num:
348 raise InvalidFile(file)
349 try:
350 fp = open(file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000351 except IOError as msg:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000352 raise NoSuchFile(file, msg)
353 try:
354 return self.entryclass(fp, file, sec_num)
355 finally:
356 fp.close()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000357
358 def show(self, file, edit=1):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000359 self.open(file).show(edit=edit)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000360
Guido van Rossumea31ea21997-05-26 05:43:29 +0000361 def new(self, section):
Georg Brandlbf82e372008-05-16 17:02:34 +0000362 if section not in SECTION_TITLES:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000363 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):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000376 self.ui = UserInput()
377 self.dir = FaqDir()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000378
379 def go(self):
Collin Winter6afaeb72007-08-03 17:06:41 +0000380 print('Content-type: text/html')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000381 req = self.ui.req or 'home'
382 mname = 'do_%s' % req
383 try:
384 meth = getattr(self, mname)
385 except AttributeError:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000386 self.error("Bad request type %r." % (req,))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000387 else:
388 try:
389 meth()
Guido van Rossumb940e112007-01-10 16:19:56 +0000390 except InvalidFile as exc:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000391 self.error("Invalid entry file name %s" % exc.file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000392 except NoSuchFile as exc:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000393 self.error("No entry with file name %s" % exc.file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000394 except NoSuchSection as exc:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000395 self.error("No section number %s" % exc.section)
396 self.epilogue()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000397
398 def error(self, message, **kw):
Guido van Rossum72dc60c1998-04-06 14:24:36 +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 Rossum72dc60c1998-04-06 14:24:36 +0000403 emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000404
405 def epilogue(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000406 emit(EPILOGUE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000407
408 def do_home(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000409 self.prologue(T_HOME)
410 emit(HOME)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000411
412 def do_debug(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000413 self.prologue("FAQ Wizard Debugging")
414 form = cgi.FieldStorage()
415 cgi.print_form(form)
Guido van Rossumea31ea21997-05-26 05:43:29 +0000416 cgi.print_environ(os.environ)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000417 cgi.print_directory()
418 cgi.print_arguments()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000419
420 def do_search(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000421 query = self.ui.query
422 if not query:
423 self.error("Empty query string!")
424 return
425 if self.ui.querytype == 'simple':
426 query = re.escape(query)
427 queries = [query]
428 elif self.ui.querytype in ('anykeywords', 'allkeywords'):
Georg Brandlbf82e372008-05-16 17:02:34 +0000429 words = [_f for _f in re.split('\W+', query) if _f]
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000430 if not words:
431 self.error("No keywords specified!")
432 return
Georg Brandlbf82e372008-05-16 17:02:34 +0000433 words = [r'\b%s\b' % w for w in words]
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000434 if self.ui.querytype[:3] == 'any':
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000435 queries = ['|'.join(words)]
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000436 else:
437 # Each of the individual queries must match
438 queries = words
439 else:
440 # Default to regular expression
441 queries = [query]
442 self.prologue(T_SEARCH)
443 progs = []
444 for query in queries:
445 if self.ui.casefold == 'no':
446 p = re.compile(query)
447 else:
448 p = re.compile(query, re.IGNORECASE)
449 progs.append(p)
450 hits = []
451 for file in self.dir.list():
452 try:
453 entry = self.dir.open(file)
454 except FileError:
455 constants
456 for p in progs:
457 if not p.search(entry.title) and not p.search(entry.body):
458 break
459 else:
460 hits.append(file)
461 if not hits:
462 emit(NO_HITS, self.ui, count=0)
463 elif len(hits) <= MAXHITS:
464 if len(hits) == 1:
465 emit(ONE_HIT, count=1)
466 else:
467 emit(FEW_HITS, count=len(hits))
468 self.format_all(hits, headers=0)
469 else:
470 emit(MANY_HITS, count=len(hits))
471 self.format_index(hits)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000472
473 def do_all(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000474 self.prologue(T_ALL)
475 files = self.dir.list()
476 self.last_changed(files)
477 self.format_index(files, localrefs=1)
478 self.format_all(files)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000479
480 def do_compat(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000481 files = self.dir.list()
482 emit(COMPAT)
483 self.last_changed(files)
484 self.format_index(files, localrefs=1)
485 self.format_all(files, edit=0)
486 sys.exit(0) # XXX Hack to suppress epilogue
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000487
488 def last_changed(self, files):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000489 latest = 0
490 for file in files:
491 entry = self.dir.open(file)
492 if entry:
493 mtime = mtime = entry.getmtime()
494 if mtime > latest:
495 latest = mtime
Collin Winter6afaeb72007-08-03 17:06:41 +0000496 print(time.strftime(LAST_CHANGED, time.localtime(latest)))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000497 emit(EXPLAIN_MARKS)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000498
Guido van Rossum21c4b5f1997-05-26 06:28:40 +0000499 def format_all(self, files, edit=1, headers=1):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000500 sec = 0
501 for file in files:
502 try:
503 entry = self.dir.open(file)
504 except NoSuchFile:
505 continue
506 if headers and entry.sec != sec:
507 sec = entry.sec
508 try:
509 title = SECTION_TITLES[sec]
510 except KeyError:
511 title = "Untitled"
512 emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
513 sec=sec, title=title)
514 entry.show(edit=edit)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000515
516 def do_index(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000517 self.prologue(T_INDEX)
518 files = self.dir.list()
519 self.last_changed(files)
520 self.format_index(files, add=1)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000521
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000522 def format_index(self, files, add=0, localrefs=0):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000523 sec = 0
524 for file in files:
525 try:
526 entry = self.dir.open(file)
527 except NoSuchFile:
528 continue
529 if entry.sec != sec:
530 if sec:
531 if add:
532 emit(INDEX_ADDSECTION, sec=sec)
533 emit(INDEX_ENDSECTION, sec=sec)
534 sec = entry.sec
535 try:
536 title = SECTION_TITLES[sec]
537 except KeyError:
538 title = "Untitled"
539 emit(INDEX_SECTION, sec=sec, title=title)
540 if localrefs:
541 emit(LOCAL_ENTRY, entry)
542 else:
543 emit(INDEX_ENTRY, entry)
544 entry.emit_marks()
545 if sec:
546 if add:
547 emit(INDEX_ADDSECTION, sec=sec)
548 emit(INDEX_ENDSECTION, sec=sec)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000549
550 def do_recent(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000551 if not self.ui.days:
552 days = 1
553 else:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000554 days = float(self.ui.days)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000555 try:
556 cutoff = now - days * 24 * 3600
557 except OverflowError:
558 cutoff = 0
559 list = []
560 for file in self.dir.list():
561 entry = self.dir.open(file)
562 if not entry:
563 continue
564 mtime = entry.getmtime()
565 if mtime >= cutoff:
566 list.append((mtime, file))
567 list.sort()
568 list.reverse()
569 self.prologue(T_RECENT)
570 if days <= 1:
571 period = "%.2g hours" % (days*24)
572 else:
573 period = "%.6g days" % days
574 if not list:
575 emit(NO_RECENT, period=period)
576 elif len(list) == 1:
577 emit(ONE_RECENT, period=period)
578 else:
579 emit(SOME_RECENT, period=period, count=len(list))
Georg Brandlbf82e372008-05-16 17:02:34 +0000580 self.format_all([mtime_file[1] for mtime_file in list], headers=0)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000581 emit(TAIL_RECENT)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000582
583 def do_roulette(self):
Guido van Rossum6c3a2cb1998-05-20 17:13:01 +0000584 import random
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000585 files = self.dir.list()
Tim Peters182b5ac2004-07-18 06:16:08 +0000586 if not files:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000587 self.error("No entries.")
588 return
Guido van Rossum6c3a2cb1998-05-20 17:13:01 +0000589 file = random.choice(files)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000590 self.prologue(T_ROULETTE)
591 emit(ROULETTE)
592 self.dir.show(file)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000593
594 def do_help(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000595 self.prologue(T_HELP)
596 emit(HELP)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000597
598 def do_show(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000599 entry = self.dir.open(self.ui.file)
600 self.prologue(T_SHOW)
601 entry.show()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000602
603 def do_add(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000604 self.prologue(T_ADD)
605 emit(ADD_HEAD)
Georg Brandlbf82e372008-05-16 17:02:34 +0000606 sections = sorted(SECTION_TITLES.items())
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000607 for section, title in sections:
608 emit(ADD_SECTION, section=section, title=title)
609 emit(ADD_TAIL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000610
611 def do_delete(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000612 self.prologue(T_DELETE)
613 emit(DELETE)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000614
615 def do_log(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000616 entry = self.dir.open(self.ui.file)
617 self.prologue(T_LOG, entry)
618 emit(LOG, entry)
619 self.rlog(interpolate(SH_RLOG, entry), entry)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000620
621 def rlog(self, command, entry=None):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000622 output = os.popen(command).read()
623 sys.stdout.write('<PRE>')
624 athead = 0
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000625 lines = output.split('\n')
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000626 while lines and not lines[-1]:
627 del lines[-1]
628 if lines:
629 line = lines[-1]
630 if line[:1] == '=' and len(line) >= 40 and \
631 line == line[0]*len(line):
632 del lines[-1]
633 headrev = None
634 for line in lines:
635 if entry and athead and line[:9] == 'revision ':
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000636 rev = line[9:].split()
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000637 mami = revparse(rev)
638 if not mami:
Collin Winter6afaeb72007-08-03 17:06:41 +0000639 print(line)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000640 else:
641 emit(REVISIONLINK, entry, rev=rev, line=line)
642 if mami[1] > 1:
643 prev = "%d.%d" % (mami[0], mami[1]-1)
644 emit(DIFFLINK, entry, prev=prev, rev=rev)
645 if headrev:
646 emit(DIFFLINK, entry, prev=rev, rev=headrev)
647 else:
648 headrev = rev
Collin Winter6afaeb72007-08-03 17:06:41 +0000649 print()
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000650 athead = 0
651 else:
652 athead = 0
653 if line[:1] == '-' and len(line) >= 20 and \
654 line == len(line) * line[0]:
655 athead = 1
656 sys.stdout.write('<HR>')
657 else:
Collin Winter6afaeb72007-08-03 17:06:41 +0000658 print(line)
659 print('</PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000660
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000661 def do_revision(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000662 entry = self.dir.open(self.ui.file)
663 rev = self.ui.rev
664 mami = revparse(rev)
665 if not mami:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000666 self.error("Invalid revision number: %r." % (rev,))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000667 self.prologue(T_REVISION, entry)
668 self.shell(interpolate(SH_REVISION, entry, rev=rev))
Guido van Rossum8bc49c81997-05-26 19:10:37 +0000669
670 def do_diff(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000671 entry = self.dir.open(self.ui.file)
672 prev = self.ui.prev
673 rev = self.ui.rev
674 mami = revparse(rev)
675 if not mami:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000676 self.error("Invalid revision number: %r." % (rev,))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000677 if prev:
678 if not revparse(prev):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000679 self.error("Invalid previous revision number: %r." % (prev,))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000680 else:
681 prev = '%d.%d' % (mami[0], mami[1])
682 self.prologue(T_DIFF, entry)
683 self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000684
685 def shell(self, command):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000686 output = os.popen(command).read()
687 sys.stdout.write('<PRE>')
Collin Winter6afaeb72007-08-03 17:06:41 +0000688 print(escape(output))
689 print('</PRE>')
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000690
691 def do_new(self):
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000692 entry = self.dir.new(section=int(self.ui.section))
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000693 entry.version = '*new*'
694 self.prologue(T_EDIT)
695 emit(EDITHEAD)
696 emit(EDITFORM1, entry, editversion=entry.version)
697 emit(EDITFORM2, entry, load_my_cookie())
698 emit(EDITFORM3)
699 entry.show(edit=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000700
701 def do_edit(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000702 entry = self.dir.open(self.ui.file)
703 entry.load_version()
704 self.prologue(T_EDIT)
705 emit(EDITHEAD)
706 emit(EDITFORM1, entry, editversion=entry.version)
707 emit(EDITFORM2, entry, load_my_cookie())
708 emit(EDITFORM3)
709 entry.show(edit=0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000710
711 def do_review(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000712 send_my_cookie(self.ui)
713 if self.ui.editversion == '*new*':
714 sec, num = self.dir.parse(self.ui.file)
715 entry = self.dir.new(section=sec)
716 entry.version = "*new*"
717 if entry.file != self.ui.file:
718 self.error("Commit version conflict!")
719 emit(NEWCONFLICT, self.ui, sec=sec, num=num)
720 return
721 else:
722 entry = self.dir.open(self.ui.file)
723 entry.load_version()
724 # Check that the FAQ entry number didn't change
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000725 if self.ui.title.split()[:1] != entry.title.split()[:1]:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000726 self.error("Don't change the entry number please!")
727 return
728 # Check that the edited version is the current version
729 if entry.version != self.ui.editversion:
730 self.error("Commit version conflict!")
731 emit(VERSIONCONFLICT, entry, self.ui)
732 return
733 commit_ok = ((not PASSWORD
Tim Peters182b5ac2004-07-18 06:16:08 +0000734 or self.ui.password == PASSWORD)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000735 and self.ui.author
736 and '@' in self.ui.email
737 and self.ui.log)
738 if self.ui.commit:
739 if not commit_ok:
740 self.cantcommit()
741 else:
742 self.commit(entry)
743 return
744 self.prologue(T_REVIEW)
745 emit(REVIEWHEAD)
746 entry.body = self.ui.body
747 entry.title = self.ui.title
748 entry.show(edit=0)
749 emit(EDITFORM1, self.ui, entry)
750 if commit_ok:
751 emit(COMMIT)
752 else:
Guido van Rossum2d3b0d71998-12-23 21:33:09 +0000753 emit(NOCOMMIT_HEAD)
754 self.errordetail()
755 emit(NOCOMMIT_TAIL)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000756 emit(EDITFORM2, self.ui, entry, load_my_cookie())
757 emit(EDITFORM3)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000758
759 def cantcommit(self):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000760 self.prologue(T_CANTCOMMIT)
Collin Winter6afaeb72007-08-03 17:06:41 +0000761 print(CANTCOMMIT_HEAD)
Guido van Rossum2d3b0d71998-12-23 21:33:09 +0000762 self.errordetail()
Collin Winter6afaeb72007-08-03 17:06:41 +0000763 print(CANTCOMMIT_TAIL)
Guido van Rossum2d3b0d71998-12-23 21:33:09 +0000764
765 def errordetail(self):
766 if PASSWORD and self.ui.password != PASSWORD:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000767 emit(NEED_PASSWD)
768 if not self.ui.log:
769 emit(NEED_LOG)
770 if not self.ui.author:
771 emit(NEED_AUTHOR)
772 if not self.ui.email:
773 emit(NEED_EMAIL)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000774
Guido van Rossumea31ea21997-05-26 05:43:29 +0000775 def commit(self, entry):
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000776 file = entry.file
777 # Normalize line endings in body
778 if '\r' in self.ui.body:
779 self.ui.body = re.sub('\r\n?', '\n', self.ui.body)
780 # Normalize whitespace in title
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000781 self.ui.title = ' '.join(self.ui.title.split())
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000782 # Check that there were any changes
783 if self.ui.body == entry.body and self.ui.title == entry.title:
784 self.error("You didn't make any changes!")
785 return
Guido van Rossum2b6004a2000-03-31 00:58:00 +0000786
787 # need to lock here because otherwise the file exists and is not writable (on NT)
788 command = interpolate(SH_LOCK, file=file)
789 p = os.popen(command)
790 output = p.read()
791
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000792 try:
793 os.unlink(file)
794 except os.error:
795 pass
796 try:
797 f = open(file, 'w')
Guido van Rossumb940e112007-01-10 16:19:56 +0000798 except IOError as why:
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000799 self.error(CANTWRITE, file=file, why=why)
800 return
801 date = time.ctime(now)
802 emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
803 f.write('\n')
804 f.write(self.ui.body)
805 f.write('\n')
806 f.close()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000807
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000808 import tempfile
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000809 tf = tempfile.NamedTemporaryFile()
Michael W. Hudsone6e77e52004-08-07 21:13:46 +0000810 emit(LOGHEADER, self.ui, os.environ, date=date, _file=tf)
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000811 tf.flush()
812 tf.seek(0)
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000813
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000814 command = interpolate(SH_CHECKIN, file=file, tfn=tf.name)
Guido van Rossum2b6004a2000-03-31 00:58:00 +0000815 log("\n\n" + command)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000816 p = os.popen(command)
817 output = p.read()
818 sts = p.close()
Guido van Rossum2b6004a2000-03-31 00:58:00 +0000819 log("output: " + output)
820 log("done: " + str(sts))
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000821 log("TempFile:\n" + tf.read() + "end")
Tim Peters182b5ac2004-07-18 06:16:08 +0000822
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000823 if not sts:
824 self.prologue(T_COMMITTED)
825 emit(COMMITTED)
826 else:
827 self.error(T_COMMITFAILED)
828 emit(COMMITFAILED, sts=sts)
Collin Winter6afaeb72007-08-03 17:06:41 +0000829 print('<PRE>%s</PRE>' % escape(output))
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000830
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000831 try:
Michael W. Hudsone6e77e52004-08-07 21:13:46 +0000832 os.unlink(tf.name)
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000833 except os.error:
834 pass
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000835
Guido van Rossum72dc60c1998-04-06 14:24:36 +0000836 entry = self.dir.open(file)
837 entry.show()
Guido van Rossum1677e5b1997-05-26 00:07:18 +0000838
839wiz = FaqWizard()
840wiz.go()