blob: bb89cca3ae99bafc7945d5b2fabe85b912eb398e [file] [log] [blame]
Guido van Rossumd7bfa801997-05-21 21:31:39 +00001"""Interactive FAQ project.
2
Guido van Rossumed531fd1997-05-22 15:21:57 +00003Note that this is not an executable script; it's an importable module.
4The actual CGI script can be kept minimal; it's appended at the end of
5this file as a string constant.
6
Guido van Rossumd7bfa801997-05-21 21:31:39 +00007XXX TO DO
8
Guido van Rossumaf5be951997-05-22 16:57:50 +00009- next/prev/index links in do_show?
Guido van Rossum74427e51997-05-21 23:43:39 +000010- should have files containing section headers
Guido van Rossumf701bf11997-05-21 22:25:56 +000011- customize rcs command pathnames
12- recognize urls and email addresses and turn them into <A> tags
Guido van Rossumd7bfa801997-05-21 21:31:39 +000013- use cookies to keep Name/email the same
14- explanation of editing somewhere
15- various embellishments, GIFs, crosslinks, hints, etc.
16- create new sections
17- rearrange entries
18- delete entries
Guido van Rossumd7bfa801997-05-21 21:31:39 +000019- send email on changes
20- optional staging of entries until reviewed?
Guido van Rossumd7bfa801997-05-21 21:31:39 +000021- freeze entries
22- username/password for editors
23- Change references to other Q's and whole sections
24- Browse should display menu of 7 sections & let you pick
25 (or frontpage should have the option to browse a section or all)
26- support adding annotations, too
Guido van Rossumed531fd1997-05-22 15:21:57 +000027- make it more generic (so you can create your own FAQ)
Guido van Rossumd7bfa801997-05-21 21:31:39 +000028
29"""
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000030
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000031NAMEPAT = "faq??.???.htp"
Guido van Rossumd7bfa801997-05-21 21:31:39 +000032NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000033
34class FAQServer:
35
36 def __init__(self):
37 pass
38
39 def main(self):
40 self.form = cgi.FieldStorage()
41 req = self.req or 'frontpage'
42 try:
43 method = getattr(self, 'do_%s' % req)
44 except AttributeError:
45 print "Unrecognized request type", req
46 else:
47 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000048 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000049
Guido van Rossum3c3354c1997-05-21 16:52:18 +000050 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000051 'author', 'email', 'log', 'section', 'number', 'add',
52 'version']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000053
54 def __getattr__(self, key):
55 if key not in self.KEYS:
56 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000057 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000058 form = self.form
59 try:
60 item = form[key]
61 except TypeError, msg:
62 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000063 except KeyError:
64 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000065 value = self.form[key].value
66 value = string.strip(value)
67 setattr(self, key, value)
68 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000069
70 def do_frontpage(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000071 self.prologue("Python FAQ (alpha) Front Page")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000072 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000073 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000074 <LI><A HREF="faq.py?req=index">FAQ index</A>
75 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +000076 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000077 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
78 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +000079 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000080 </UL>
81
Guido van Rossumd7bfa801997-05-21 21:31:39 +000082 <H2>Search the FAQ</H2>
83
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000084 <FORM ACTION="faq.py?req=query">
85 <INPUT TYPE=text NAME=query>
86 <INPUT TYPE=submit VALUE="Search">
87 <INPUT TYPE=hidden NAME=req VALUE=query>
88 </FORM>
89
90 Disclaimer: these pages are intended to be edited by anyone.
91 Please exercise discretion when editing, don't be rude, etc.
92 """
93
Guido van Rossumd7bfa801997-05-21 21:31:39 +000094 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000095 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +000096 names = os.listdir(os.curdir)
97 names.sort()
98 section = None
99 for name in names:
100 headers, text = self.read(name)
101 if headers:
102 title = headers['title']
103 i = string.find(title, '.')
104 nsec = title[:i]
105 if nsec != section:
106 if section:
107 print """
108 <P>
109 <LI><A HREF="faq.py?req=add&amp;section=%s"
110 >Add new entry</A> (at this point)
111 </UL>
112 """ % section
113 section = nsec
114 print "<H2>Section %s</H2>" % section
115 print "<UL>"
116 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
117 name, cgi.escape(title))
118 if section:
119 print """
120 <P>
121 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
122 (at this point)
123 </UL>
124 """ % section
125 else:
126 print "No FAQ entries?!?!"
127
128 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000129 self.prologue("Python FAQ Entry")
130 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000131 name = self.name
132 headers, text = self.read(name)
133 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000134 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000135 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000136 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000137
138 def do_all(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000139 self.prologue("The Whole Python FAQ")
140 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000141 names = os.listdir(os.curdir)
142 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000143 section = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000144 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000145 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000146 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000147 title = headers['title']
148 i = string.find(title, '.')
149 nsec = title[:i]
150 if nsec != section:
151 section = nsec
152 print "<H1>Section %s</H1>" % section
153 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000154 self.show(name, title, text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000155 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000156 print "No FAQ entries?!?!"
157
158 def do_roulette(self):
159 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000160 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000161 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000162 Please check the correctness of the entry below.
163 If you find any problems, please edit the entry.
164 <P>
165 <HR>
166 """
167 names = os.listdir(os.curdir)
168 while names:
169 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000170 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000171 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000172 self.show(name, headers['title'], text)
173 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000174 break
175 else:
176 names.remove(name)
177 else:
178 print "No FAQ entries?!?!"
179
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000180 def do_recent(self):
181 import fnmatch, stat
182 names = os.listdir(os.curdir)
183 now = time.time()
184 list = []
185 for name in names:
186 if not fnmatch.fnmatch(name, NAMEPAT):
187 continue
188 try:
189 st = os.stat(name)
190 except os.error:
191 continue
192 tuple = (st[stat.ST_MTIME], name)
193 list.append(tuple)
194 list.sort()
195 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000196 self.prologue("Python FAQ, Most Recently Modified First")
197 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000198 n = 0
199 for (mtime, name) in list:
200 headers, text = self.read(name)
201 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000202 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000203 n = n+1
204 if not n:
205 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000206
207 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000208 query = self.query
209 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000210 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000211 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000212 import regex
213 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000214 p = regex.compile(query, regex.casefold)
215 names = os.listdir(os.curdir)
216 names.sort()
217 print "<HR>"
218 n = 0
219 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000220 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000221 if headers:
222 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000223 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000224 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000225 n = n+1
226 if not n:
227 print "No hits."
228
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000229 def do_add(self):
230 section = self.section
231 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000232 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000233 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000234 Go to the <A HREF="faq.py?req=index">FAQ index</A>
235 and click on the "Add new entry" link at the end
236 of the section to which you want to add the entry.
237 """
238 return
239 try:
240 nsec = string.atoi(section)
241 except ValueError:
242 print "Bad section number", nsec
243 names = os.listdir(os.curdir)
244 max = 0
245 import regex
246 prog = regex.compile(NAMEREG)
247 for name in names:
248 if prog.match(name) >= 0:
249 s1, s2 = prog.group(1, 2)
250 n1, n2 = string.atoi(s1), string.atoi(s2)
251 if n1 == nsec:
252 if n2 > max:
253 max = n2
254 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000255 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000256 return
257 num = max+1
258 name = "faq%02d.%03d.htp" % (nsec, num)
259 self.name = name
260 self.add = "yes"
261 self.number = str(num)
262 self.do_edit()
263
Guido van Rossumaf5be951997-05-22 16:57:50 +0000264 def do_delete(self):
265 self.prologue("How to delete a FAQ entry")
266 print """
267 At the moment, there's no direct way to delete entries.
268 This is because the entry numbers are also their
269 unique identifiers -- it's a bad idea to renumber entries.
270 <P>
271 If you really think an entry needs to be deleted,
272 change the title to "(deleted)" and make the body
273 empty (keep the entry number in the title though).
274 """
275
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000276 def do_edit(self):
277 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000278 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000279 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000280 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000281 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000282 self.prologue("Python FAQ Edit Form")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000283 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000284 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000285 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000286 self.showedit(name, title, text)
287 if self.add:
288 print """
289 <INPUT TYPE=hidden NAME=add VALUE=%s>
290 <INPUT TYPE=hidden NAME=section VALUE=%s>
291 <INPUT TYPE=hidden NAME=number VALUE=%s>
292 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000293 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000294 <INPUT TYPE=submit VALUE="Review Edit">
295 <INPUT TYPE=hidden NAME=req VALUE=review>
296 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000297 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000298 </FORM>
299 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000300 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000301 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000302
303 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000304 if self.commit:
305 self.checkin()
306 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000307 name = self.name
308 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000309 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000310 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000311 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000312 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000313 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000314 if self.author and '@' in self.email:
315 self.set_cookie(self.author, self.email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000316 self.prologue("Python FAQ Review Form")
317 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000318 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000319 print "<FORM METHOD=POST ACTION=faq.py>"
320 if self.log and self.author and '@' in self.email:
321 print """
322 <INPUT TYPE=submit NAME=commit VALUE="Commit">
323 Click this button to commit the change.
324 <P>
325 <HR>
326 <P>
327 """
328 else:
329 print """
330 To commit this change, please enter your name,
331 email and a log message in the form below.
332 <P>
333 <HR>
334 <P>
335 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000336 self.showedit(name, title, text)
337 if self.add:
338 print """
339 <INPUT TYPE=hidden NAME=add VALUE=%s>
340 <INPUT TYPE=hidden NAME=section VALUE=%s>
341 <INPUT TYPE=hidden NAME=number VALUE=%s>
342 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000343 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000344 <BR>
345 <INPUT TYPE=submit VALUE="Review Edit">
346 <INPUT TYPE=hidden NAME=req VALUE=review>
347 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000348 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000349 </FORM>
350 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000351 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000352
Guido van Rossumf701bf11997-05-21 22:25:56 +0000353 def do_info(self):
354 name = self.name
355 headers, text = self.read(name)
356 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000357 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000358 return
359 print '<PRE>'
360 sys.stdout.flush()
361 os.system("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" % self.name)
362 print '</PRE>'
363 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
364
365 def do_rlog(self):
366 name = self.name
367 headers, text = self.read(name)
368 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000369 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000370 return
371 print '<PRE>'
372 sys.stdout.flush()
373 os.system("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
374 print '</PRE>'
375
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000376 def checkin(self):
377 import regsub, time, tempfile
378 name = self.name
379
380 headers, oldtext = self.read(name)
381 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000382 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000383 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000384 version = self.version
385 curversion = self.getversion(name)
386 if version != curversion:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000387 self.error("Version conflict.",
388 "You edited version %s but current version is %s." % (
389 version, curversion),
390 '<A HREF="faq.py?req=show&name=%s">Reload.</A>' % name)
Guido van Rossum74427e51997-05-21 23:43:39 +0000391 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000392 text = self.text
393 title = self.title
394 author = self.author
395 email = self.email
396 log = self.log
397 text = regsub.gsub("\r\n", "\n", text)
398 log = regsub.gsub("\r\n", "\n", log)
399 author = string.join(string.split(author))
400 email = string.join(string.split(email))
401 title = string.join(string.split(title))
402 oldtitle = headers['title']
403 oldtitle = string.join(string.split(oldtitle))
404 text = string.strip(text)
405 oldtext = string.strip(oldtext)
406 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000407 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000408 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000409 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000410 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000411 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000412 return
413 remhost = os.environ["REMOTE_HOST"]
414 remaddr = os.environ["REMOTE_ADDR"]
415 try:
416 os.unlink(name + "~")
417 except os.error:
418 pass
419 try:
420 os.rename(name, name + "~")
421 except os.error:
422 pass
423 try:
424 os.unlink(name)
425 except os.error:
426 pass
427 try:
428 f = open(name, "w")
429 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000430 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000431 return
432 now = time.ctime(time.time())
433 f.write("Title: %s\n" % title)
434 f.write("Last-Changed-Date: %s\n" % now)
435 f.write("Last-Changed-Author: %s\n" % author)
436 f.write("Last-Changed-Email: %s\n" % email)
437 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
438 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
439 keys = headers.keys()
440 keys.sort()
441 keys.remove('title')
442 for key in keys:
443 if key[:13] != 'last-changed-':
444 f.write("%s: %s\n" % (string.capwords(key, '-'),
445 headers[key]))
446 f.write("\n")
447 f.write(text)
448 f.write("\n")
449 f.close()
450
451 tfn = tempfile.mktemp()
452 f = open(tfn, "w")
453 f.write("Last-Changed-Date: %s\n" % now)
454 f.write("Last-Changed-Author: %s\n" % author)
455 f.write("Last-Changed-Email: %s\n" % email)
456 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
457 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
458 f.write("\n")
459 f.write(log)
460 f.write("\n")
461 f.close()
462
463 p = os.popen("""
464 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
465 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
466 rm -f %s
467 """ % (name, name, tfn, tfn))
468 output = p.read()
469 sts = p.close()
470 if not sts:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000471 self.set_cookie(author, email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000472 self.prologue("Python FAQ Entry Edited")
473 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000474 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000475 if output:
476 print "<PRE>%s</PRE>" % cgi.escape(output)
477 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000478 self.error("Python FAQ Entry Commit Failed",
479 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000480 if output:
481 print "<PRE>%s</PRE>" % cgi.escape(output)
Guido van Rossum64099e91997-05-22 15:49:23 +0000482 print '<HR>'
483 print '<A HREF="faq.py?req=show&name=%s">Reload this entry.</A>' % name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000484
Guido van Rossumaf5be951997-05-22 16:57:50 +0000485 def set_cookie(self, author, email):
486 name = "Python-FAQ-ID"
487 value = "%s;%s" % (author, email)
488 import urllib
489 value = urllib.quote(value)
490 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
491 print "domain=%s;" % os.environ['HTTP_HOST'],
492 print "expires=Sat, 01-Jan-2000 00:00:00 GMT"
493
494 def get_cookie(self):
495 if not os.environ.has_key('HTTP_COOKIE'):
496 return "", ""
497 raw = os.environ['HTTP_COOKIE']
498 words = string.split(raw, ';')
499 cookies = {}
500 for word in words:
501 i = string.find(word, '=')
502 if i >= 0:
503 key, value = word[:i], word[i+1:]
504 cookies[key] = value
505 if not cookies.has_key('Python-FAQ-ID'):
506 return "", ""
507 value = cookies['Python-FAQ-ID']
508 import urllib
509 value = urllib.unquote(value)
510 i = string.rfind(value, ';')
511 author, email = value[:i], value[i+1:]
512 return author, email
513
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000514 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000515 author = self.author
516 email = self.email
517 if not author or not email:
518 a, e = self.get_cookie()
519 author = author or a
520 email = email or e
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000521 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000522 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000523 <TEXTAREA COLS=80 ROWS=20 NAME=text>""" % title
524 print cgi.escape(string.strip(text))
525 print """</TEXTAREA>
526 <BR>
527 Please provide the following information for logging purposes:
528 <BR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000529 <CODE>Name : </CODE><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
530 <BR>
531 <CODE>Email: </CODE><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
532 <BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000533 Log message (reason for the change):<BR>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000534 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA>
535 """ % (author, email, self.log)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000536
537 def showheaders(self, headers):
538 print "<UL>"
539 keys = map(string.lower, headers.keys())
540 keys.sort()
541 for key in keys:
542 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
543 headers[key] or '')
544 print "</UL>"
545
Guido van Rossumed531fd1997-05-22 15:21:57 +0000546 headers = None
547
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000548 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000549 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000550 import fnmatch, rfc822
551 if not fnmatch.fnmatch(name, NAMEPAT):
552 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000553 if self.add:
554 try:
555 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
556 string.atoi(self.number))
557 except ValueError:
558 return None, None
559 if fname != name:
560 return None, None
561 headers = {'title': "%s.%s. " % (self.section, self.number)}
562 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000563 else:
564 f = open(name)
565 headers = rfc822.Message(f)
566 text = f.read()
567 f.close()
568 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000569 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000570
Guido van Rossumed531fd1997-05-22 15:21:57 +0000571 def show(self, name, title, text, edit=1):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000572 # XXX Should put <A> tags around recognizable URLs
573 # XXX Should also turn "see section N" into hyperlinks
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000574 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000575 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000576 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000577 if not string.strip(line):
578 if pre:
579 print '</PRE>'
580 pre = 0
581 else:
582 print '<P>'
583 else:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000584 if line == string.lstrip(line): # I.e., no leading whitespace
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000585 if pre:
586 print '</PRE>'
587 pre = 0
588 else:
589 if not pre:
590 print '<PRE>'
591 pre = 1
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000592 print cgi.escape(line)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000593 if pre:
594 print '</PRE>'
595 pre = 0
596 print '<P>'
597 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000598 print """
599 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
600 <A HREF="faq.py?req=info&name=%s" TARGET=_blank>Log info</A>
601 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000602 if self.headers:
603 try:
604 date = self.headers['last-changed-date']
605 author = self.headers['last-changed-author']
606 email = self.headers['last-changed-email']
607 except KeyError:
608 pass
609 else:
Guido van Rossum64099e91997-05-22 15:49:23 +0000610 s = '/ Last changed on %s by <A HREF="%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000611 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000612 print '<P>'
613 print "<HR>"
614
Guido van Rossum74427e51997-05-21 23:43:39 +0000615 def getversion(self, name):
616 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000617 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000618 while 1:
619 line = p.readline()
620 if not line:
621 break
622 if line[:5] == 'head:':
623 head = string.strip(line[5:])
624 p.close()
625 return head
626
627 def prologue(self, title):
628 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000629 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000630 <HTML>
631 <HEAD>
632 <TITLE>%s</TITLE>
633 </HEAD>
634 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
635 BGCOLOR="#FFFFFF"
636 TEXT="#000000"
637 LINK="#AA0000"
638 VLINK="#906A6A">
639 <H1>%s</H1>
640 ''' % (title, title)
641
Guido van Rossumaf5be951997-05-22 16:57:50 +0000642 def error(self, *messages):
643 self.prologue("Python FAQ error")
644 print "Sorry, an error occurred:<BR>"
645 for message in messages:
646 print message,
647 print
648
Guido van Rossum74427e51997-05-21 23:43:39 +0000649 def epilogue(self):
650 print '''
651 <P>
652 <HR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000653 <A HREF="http://www.python.org">Python home</A> /
654 <A HREF="faq.py">FAQ home</A> /
Guido van Rossum64099e91997-05-22 15:49:23 +0000655 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
Guido van Rossum74427e51997-05-21 23:43:39 +0000656 </BODY>
657 </HTML>
658 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000659
Guido van Rossumaf5be951997-05-22 16:57:50 +0000660print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000661dt = 0
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000662try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000663 import time
664 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000665 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000666 x = FAQServer()
667 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000668 t2 = time.time()
669 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000670except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000671 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000672 cgi.print_exception()
Guido van Rossum74427e51997-05-21 23:43:39 +0000673print "<P>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000674
675# The following bootstrap script must be placed in cgi-bin/faq.py:
676BOOTSTRAP = """
677#! /usr/local/bin/python
678FAQDIR = "/usr/people/guido/python/FAQ"
679import os, sys
680os.chdir(FAQDIR)
681sys.path.insert(0, os.curdir)
682import faqmain
683"""