blob: 12eca59a4db416bba974ceed257add59df7cffcb [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 Rossum1dcc2441997-05-23 18:53:06 +00009XXX User Features TO DO
10
Guido van Rossumaf5be951997-05-22 16:57:50 +000011- next/prev/index links in do_show?
Guido van Rossumd7bfa801997-05-21 21:31:39 +000012- explanation of editing somewhere
Guido van Rossum1dcc2441997-05-23 18:53:06 +000013- embellishments, GIFs, crosslinks, hints, etc.
14- make references to other Q's and whole sections into links
15- support adding annotations, too
16
17XXX Management Features TO DO
18
Guido van Rossumd7bfa801997-05-21 21:31:39 +000019- create new sections
20- rearrange entries
21- delete entries
Guido van Rossum1dcc2441997-05-23 18:53:06 +000022- send email on changes?
23- send email on ERRORS!
Guido van Rossumd7bfa801997-05-21 21:31:39 +000024- optional staging of entries until reviewed?
Guido van Rossumd7bfa801997-05-21 21:31:39 +000025- freeze entries
Guido van Rossum1dcc2441997-05-23 18:53:06 +000026- username/password for authors
27- read section titles from a file (could be a Python file: import faqcustom)
28
29XXX Code organization TO DO
30
31- customize rcs command pathnames (and everything else)
Guido van Rossumed531fd1997-05-22 15:21:57 +000032- make it more generic (so you can create your own FAQ)
Guido van Rossumc6447521997-05-23 00:50:01 +000033- more OO structure, e.g. add a class representing one FAQ entry
Guido van Rossumd7bfa801997-05-21 21:31:39 +000034
35"""
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000036
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000037NAMEPAT = "faq??.???.htp"
Guido van Rossumd7bfa801997-05-21 21:31:39 +000038NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000039
Guido van Rossum4888c7e1997-05-23 15:55:19 +000040SECTIONS = {
41 "1": "General information and availability",
42 "2": "Python in the real world",
43 "3": "Building Python and Other Known Bugs",
44 "4": "Programming in Python",
45 "5": "Extending Python",
46 "6": "Python's design",
47 "7": "Using Python on non-UNIX platforms",
48}
49
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000050class FAQServer:
51
52 def __init__(self):
53 pass
54
55 def main(self):
56 self.form = cgi.FieldStorage()
57 req = self.req or 'frontpage'
58 try:
59 method = getattr(self, 'do_%s' % req)
60 except AttributeError:
61 print "Unrecognized request type", req
62 else:
63 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000064 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000065
Guido van Rossum3c3354c1997-05-21 16:52:18 +000066 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000067 'author', 'email', 'log', 'section', 'number', 'add',
Guido van Rossum4888c7e1997-05-23 15:55:19 +000068 'version', 'edit']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000069
70 def __getattr__(self, key):
71 if key not in self.KEYS:
72 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000073 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000074 form = self.form
75 try:
76 item = form[key]
77 except TypeError, msg:
78 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000079 except KeyError:
80 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000081 value = self.form[key].value
82 value = string.strip(value)
83 setattr(self, key, value)
84 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000085
86 def do_frontpage(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000087 self.prologue("Python FAQ (alpha) Front Page")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000088 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000089 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000090 <LI><A HREF="faq.py?req=index">FAQ index</A>
91 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +000092 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000093 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
94 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +000095 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000096 </UL>
97
Guido van Rossumd1c1ec81997-05-23 17:45:04 +000098 <HR>
99
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000100 <H2>Search the FAQ</H2>
101
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000102 <FORM ACTION="faq.py">
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000103 <INPUT TYPE=text NAME=query>
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000104 <INPUT TYPE=submit VALUE="Search"><BR>
105 (Case insensitive regular expressions.)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000106 <INPUT TYPE=hidden NAME=req VALUE=query>
107 </FORM>
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000108 <HR>
109 <P>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000110 Disclaimer: these pages are intended to be edited by anyone.
111 Please exercise discretion when editing, don't be rude, etc.
112 """
113
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000114 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000115 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000116 names = os.listdir(os.curdir)
117 names.sort()
118 section = None
119 for name in names:
120 headers, text = self.read(name)
121 if headers:
122 title = headers['title']
123 i = string.find(title, '.')
124 nsec = title[:i]
125 if nsec != section:
126 if section:
127 print """
128 <P>
129 <LI><A HREF="faq.py?req=add&amp;section=%s"
130 >Add new entry</A> (at this point)
131 </UL>
132 """ % section
133 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000134 if SECTIONS.has_key(section):
135 stitle = SECTIONS[section]
136 else:
137 stitle = ""
138 print "<H2>Section %s. %s</H2>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000139 print "<UL>"
140 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
141 name, cgi.escape(title))
142 if section:
143 print """
144 <P>
145 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
146 (at this point)
147 </UL>
148 """ % section
149 else:
150 print "No FAQ entries?!?!"
151
152 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000153 self.prologue("Python FAQ Entry")
154 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000155 name = self.name
156 headers, text = self.read(name)
157 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000158 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000159 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000160 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000161
162 def do_all(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000163 self.prologue("The Whole Python FAQ")
164 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000165 names = os.listdir(os.curdir)
166 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000167 section = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000168 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000169 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000170 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000171 title = headers['title']
172 i = string.find(title, '.')
173 nsec = title[:i]
174 if nsec != section:
175 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000176 if SECTIONS.has_key(section):
177 stitle = SECTIONS[section]
178 else:
179 stitle = ""
180 print "<H1>Section %s. %s</H1>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000181 print "<HR>"
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000182 self.show(name, title, text, edit=(self.edit != 'no'))
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000183 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000184 print "No FAQ entries?!?!"
185
186 def do_roulette(self):
187 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000188 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000189 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000190 Please check the correctness of the entry below.
191 If you find any problems, please edit the entry.
192 <P>
193 <HR>
194 """
195 names = os.listdir(os.curdir)
196 while names:
197 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000198 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000199 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000200 self.show(name, headers['title'], text)
201 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000202 break
203 else:
204 names.remove(name)
205 else:
206 print "No FAQ entries?!?!"
207
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000208 def do_recent(self):
209 import fnmatch, stat
210 names = os.listdir(os.curdir)
211 now = time.time()
212 list = []
213 for name in names:
214 if not fnmatch.fnmatch(name, NAMEPAT):
215 continue
216 try:
217 st = os.stat(name)
218 except os.error:
219 continue
220 tuple = (st[stat.ST_MTIME], name)
221 list.append(tuple)
222 list.sort()
223 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000224 self.prologue("Python FAQ, Most Recently Modified First")
225 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000226 n = 0
227 for (mtime, name) in list:
228 headers, text = self.read(name)
Guido van Rossumc6447521997-05-23 00:50:01 +0000229 if headers and headers.has_key('last-changed-date'):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000230 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000231 n = n+1
232 if not n:
233 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000234
235 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000236 query = self.query
237 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000238 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000239 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000240 import regex
241 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000242 p = regex.compile(query, regex.casefold)
243 names = os.listdir(os.curdir)
244 names.sort()
245 print "<HR>"
246 n = 0
247 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000248 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000249 if headers:
250 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000251 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000252 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000253 n = n+1
254 if not n:
255 print "No hits."
256
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000257 def do_add(self):
258 section = self.section
259 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000260 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000261 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000262 Go to the <A HREF="faq.py?req=index">FAQ index</A>
263 and click on the "Add new entry" link at the end
264 of the section to which you want to add the entry.
265 """
266 return
267 try:
268 nsec = string.atoi(section)
269 except ValueError:
270 print "Bad section number", nsec
271 names = os.listdir(os.curdir)
272 max = 0
273 import regex
274 prog = regex.compile(NAMEREG)
275 for name in names:
276 if prog.match(name) >= 0:
277 s1, s2 = prog.group(1, 2)
278 n1, n2 = string.atoi(s1), string.atoi(s2)
279 if n1 == nsec:
280 if n2 > max:
281 max = n2
282 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000283 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000284 return
285 num = max+1
286 name = "faq%02d.%03d.htp" % (nsec, num)
287 self.name = name
288 self.add = "yes"
289 self.number = str(num)
290 self.do_edit()
291
Guido van Rossumaf5be951997-05-22 16:57:50 +0000292 def do_delete(self):
293 self.prologue("How to delete a FAQ entry")
294 print """
295 At the moment, there's no direct way to delete entries.
296 This is because the entry numbers are also their
297 unique identifiers -- it's a bad idea to renumber entries.
298 <P>
299 If you really think an entry needs to be deleted,
300 change the title to "(deleted)" and make the body
301 empty (keep the entry number in the title though).
302 """
303
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000304 def do_edit(self):
305 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000306 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000307 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000308 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000309 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000310 self.prologue("Python FAQ Edit Form")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000311 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000312 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000313 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000314 self.showedit(name, title, text)
315 if self.add:
316 print """
317 <INPUT TYPE=hidden NAME=add VALUE=%s>
318 <INPUT TYPE=hidden NAME=section VALUE=%s>
319 <INPUT TYPE=hidden NAME=number VALUE=%s>
320 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000321 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000322 <INPUT TYPE=submit VALUE="Review Edit">
323 <INPUT TYPE=hidden NAME=req VALUE=review>
324 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000325 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000326 </FORM>
327 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000328 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000329 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000330
331 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000332 if self.commit:
333 self.checkin()
334 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000335 name = self.name
336 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000337 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000338 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000339 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000340 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000341 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000342 if self.author and '@' in self.email:
343 self.set_cookie(self.author, self.email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000344 self.prologue("Python FAQ Review Form")
345 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000346 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000347 print "<FORM METHOD=POST ACTION=faq.py>"
348 if self.log and self.author and '@' in self.email:
349 print """
350 <INPUT TYPE=submit NAME=commit VALUE="Commit">
351 Click this button to commit the change.
352 <P>
353 <HR>
354 <P>
355 """
356 else:
357 print """
358 To commit this change, please enter your name,
359 email and a log message in the form below.
360 <P>
361 <HR>
362 <P>
363 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000364 self.showedit(name, title, text)
365 if self.add:
366 print """
367 <INPUT TYPE=hidden NAME=add VALUE=%s>
368 <INPUT TYPE=hidden NAME=section VALUE=%s>
369 <INPUT TYPE=hidden NAME=number VALUE=%s>
370 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000371 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000372 <BR>
373 <INPUT TYPE=submit VALUE="Review Edit">
374 <INPUT TYPE=hidden NAME=req VALUE=review>
375 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000376 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000377 </FORM>
378 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000379 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000380
Guido van Rossumf701bf11997-05-21 22:25:56 +0000381 def do_info(self):
382 name = self.name
383 headers, text = self.read(name)
384 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000385 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000386 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000387 self.prologue("Info for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000388 print '<PRE>'
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000389 p = os.popen("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" %
390 self.name)
391 output = p.read()
392 p.close()
393 print cgi.escape(output)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000394 print '</PRE>'
395 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
396
397 def do_rlog(self):
398 name = self.name
399 headers, text = self.read(name)
400 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000401 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000402 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000403 self.prologue("RCS log for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000404 print '<PRE>'
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000405 p = os.popen("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
406 output = p.read()
407 p.close()
408 print cgi.escape(output)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000409 print '</PRE>'
410
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000411 def checkin(self):
412 import regsub, time, tempfile
413 name = self.name
414
415 headers, oldtext = self.read(name)
416 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000417 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000418 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000419 version = self.version
420 curversion = self.getversion(name)
421 if version != curversion:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000422 self.error("Version conflict.",
423 "You edited version %s but current version is %s." % (
424 version, curversion),
425 '<A HREF="faq.py?req=show&name=%s">Reload.</A>' % name)
Guido van Rossum74427e51997-05-21 23:43:39 +0000426 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000427 text = self.text
428 title = self.title
429 author = self.author
430 email = self.email
431 log = self.log
432 text = regsub.gsub("\r\n", "\n", text)
433 log = regsub.gsub("\r\n", "\n", log)
434 author = string.join(string.split(author))
435 email = string.join(string.split(email))
436 title = string.join(string.split(title))
437 oldtitle = headers['title']
438 oldtitle = string.join(string.split(oldtitle))
439 text = string.strip(text)
440 oldtext = string.strip(oldtext)
441 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000442 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000443 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000444 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000445 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000446 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000447 return
448 remhost = os.environ["REMOTE_HOST"]
449 remaddr = os.environ["REMOTE_ADDR"]
450 try:
451 os.unlink(name + "~")
452 except os.error:
453 pass
454 try:
455 os.rename(name, name + "~")
456 except os.error:
457 pass
458 try:
459 os.unlink(name)
460 except os.error:
461 pass
462 try:
463 f = open(name, "w")
464 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000465 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000466 return
467 now = time.ctime(time.time())
468 f.write("Title: %s\n" % title)
469 f.write("Last-Changed-Date: %s\n" % now)
470 f.write("Last-Changed-Author: %s\n" % author)
471 f.write("Last-Changed-Email: %s\n" % email)
472 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
473 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
474 keys = headers.keys()
475 keys.sort()
476 keys.remove('title')
477 for key in keys:
478 if key[:13] != 'last-changed-':
479 f.write("%s: %s\n" % (string.capwords(key, '-'),
480 headers[key]))
481 f.write("\n")
482 f.write(text)
483 f.write("\n")
484 f.close()
485
486 tfn = tempfile.mktemp()
487 f = open(tfn, "w")
488 f.write("Last-Changed-Date: %s\n" % now)
489 f.write("Last-Changed-Author: %s\n" % author)
490 f.write("Last-Changed-Email: %s\n" % email)
491 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
492 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
493 f.write("\n")
494 f.write(log)
495 f.write("\n")
496 f.close()
497
498 p = os.popen("""
499 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
500 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
501 rm -f %s
502 """ % (name, name, tfn, tfn))
503 output = p.read()
504 sts = p.close()
505 if not sts:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000506 self.set_cookie(author, email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000507 self.prologue("Python FAQ Entry Edited")
508 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000509 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000510 if output:
511 print "<PRE>%s</PRE>" % cgi.escape(output)
512 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000513 self.error("Python FAQ Entry Commit Failed",
514 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000515 if output:
516 print "<PRE>%s</PRE>" % cgi.escape(output)
Guido van Rossum64099e91997-05-22 15:49:23 +0000517 print '<HR>'
518 print '<A HREF="faq.py?req=show&name=%s">Reload this entry.</A>' % name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000519
Guido van Rossumaf5be951997-05-22 16:57:50 +0000520 def set_cookie(self, author, email):
521 name = "Python-FAQ-ID"
522 value = "%s;%s" % (author, email)
523 import urllib
524 value = urllib.quote(value)
525 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
Guido van Rossum1d579811997-05-23 19:18:35 +0000526 import time
527 now = time.time()
528 then = now + 28 * 24 * 3600
529 gmt = time.gmtime(then)
530 print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000531
532 def get_cookie(self):
533 if not os.environ.has_key('HTTP_COOKIE'):
534 return "", ""
535 raw = os.environ['HTTP_COOKIE']
536 words = string.split(raw, ';')
537 cookies = {}
538 for word in words:
539 i = string.find(word, '=')
540 if i >= 0:
541 key, value = word[:i], word[i+1:]
542 cookies[key] = value
543 if not cookies.has_key('Python-FAQ-ID'):
544 return "", ""
545 value = cookies['Python-FAQ-ID']
546 import urllib
547 value = urllib.unquote(value)
548 i = string.rfind(value, ';')
549 author, email = value[:i], value[i+1:]
550 return author, email
551
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000552 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000553 author = self.author
554 email = self.email
555 if not author or not email:
556 a, e = self.get_cookie()
557 author = author or a
558 email = email or e
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000559 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000560 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000561 <TEXTAREA COLS=80 ROWS=20 NAME=text>""" % self.escape(title)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000562 print cgi.escape(string.strip(text))
563 print """</TEXTAREA>
564 <BR>
565 Please provide the following information for logging purposes:
566 <BR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000567 <CODE>Name : </CODE><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
568 <BR>
569 <CODE>Email: </CODE><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
570 <BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000571 Log message (reason for the change):<BR>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000572 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA>
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000573 """ % (self.escape(author), self.escape(email), self.escape(self.log))
574
575 def escape(self, s):
576 import regsub
577 if '&' in s:
578 s = regsub.gsub("&", "&amp;", s) # Must be done first!
579 if '<' in s:
580 s = regsub.gsub("<", "&lt;", s)
581 if '>' in s:
582 s = regsub.gsub(">", "&gt;", s)
583 if '"' in s:
584 s = regsub.gsub('"', "&quot;", s)
585 return s
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000586
587 def showheaders(self, headers):
588 print "<UL>"
589 keys = map(string.lower, headers.keys())
590 keys.sort()
591 for key in keys:
592 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
593 headers[key] or '')
594 print "</UL>"
595
Guido van Rossumed531fd1997-05-22 15:21:57 +0000596 headers = None
597
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000598 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000599 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000600 import fnmatch, rfc822
601 if not fnmatch.fnmatch(name, NAMEPAT):
602 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000603 if self.add:
604 try:
605 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
606 string.atoi(self.number))
607 except ValueError:
608 return None, None
609 if fname != name:
610 return None, None
611 headers = {'title': "%s.%s. " % (self.section, self.number)}
612 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000613 else:
614 f = open(name)
615 headers = rfc822.Message(f)
616 text = f.read()
617 f.close()
618 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000619 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000620
Guido van Rossumed531fd1997-05-22 15:21:57 +0000621 def show(self, name, title, text, edit=1):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000622 # XXX Should put <A> tags around recognizable URLs
623 # XXX Should also turn "see section N" into hyperlinks
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000624 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000625 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000626 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000627 if not string.strip(line):
628 if pre:
629 print '</PRE>'
630 pre = 0
631 else:
632 print '<P>'
633 else:
Guido van Rossum5527db51997-05-23 04:44:30 +0000634 if line[0] not in string.whitespace:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000635 if pre:
636 print '</PRE>'
637 pre = 0
638 else:
639 if not pre:
640 print '<PRE>'
641 pre = 1
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000642 if '/' in line or '@' in line:
643 line = self.translate(line)
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000644 elif '<' in line or '&' in line:
645 line = cgi.escape(line)
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000646 if not pre and '*' in line:
647 line = self.emphasize(line)
648 print line
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000649 if pre:
650 print '</PRE>'
651 pre = 0
652 print '<P>'
653 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000654 print """
655 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
Guido van Rossum1d579811997-05-23 19:18:35 +0000656 <A HREF="faq.py?req=info&name=%s" TARGET=rlog>Log info</A>
Guido van Rossumf701bf11997-05-21 22:25:56 +0000657 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000658 if self.headers:
659 try:
660 date = self.headers['last-changed-date']
661 author = self.headers['last-changed-author']
662 email = self.headers['last-changed-email']
663 except KeyError:
664 pass
665 else:
Guido van Rossum1d579811997-05-23 19:18:35 +0000666 s = '/ Last changed on %s by <A HREF="mailto:%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000667 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000668 print '<P>'
669 print "<HR>"
670
Guido van Rossum74427e51997-05-21 23:43:39 +0000671 def getversion(self, name):
672 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000673 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000674 while 1:
675 line = p.readline()
676 if not line:
677 break
678 if line[:5] == 'head:':
679 head = string.strip(line[5:])
680 p.close()
681 return head
682
683 def prologue(self, title):
684 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000685 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000686 <HTML>
687 <HEAD>
688 <TITLE>%s</TITLE>
689 </HEAD>
690 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
691 BGCOLOR="#FFFFFF"
692 TEXT="#000000"
693 LINK="#AA0000"
694 VLINK="#906A6A">
695 <H1>%s</H1>
696 ''' % (title, title)
697
Guido van Rossumaf5be951997-05-22 16:57:50 +0000698 def error(self, *messages):
699 self.prologue("Python FAQ error")
700 print "Sorry, an error occurred:<BR>"
701 for message in messages:
702 print message,
703 print
704
Guido van Rossum74427e51997-05-21 23:43:39 +0000705 def epilogue(self):
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000706 if self.edit == 'no':
707 global wanttime
708 wanttime = 0
709 else:
710 print '''
711 <P>
712 <HR>
713 <A HREF="http://www.python.org">Python home</A> /
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000714 <A HREF="faq.py?req=frontpage">FAQ home</A> /
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000715 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
716 '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000717 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000718 </BODY>
719 </HTML>
720 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000721
Guido van Rossum5527db51997-05-23 04:44:30 +0000722 translate_prog = None
723
724 def translate(self, text):
725 if not self.translate_prog:
726 import regex
727 url = '\(http\|ftp\)://[^ \t\r\n]*'
728 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
729 self.translate_prog = prog = regex.compile(url + "\|" + email)
730 else:
731 prog = self.translate_prog
732 i = 0
733 list = []
734 while 1:
735 j = prog.search(text, i)
736 if j < 0:
737 break
738 list.append(cgi.escape(text[i:j]))
739 i = j
740 url = prog.group(0)
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000741 while url[-1] in ");:,.?'\"":
Guido van Rossum5527db51997-05-23 04:44:30 +0000742 url = url[:-1]
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000743 url = self.escape(url)
Guido van Rossum5527db51997-05-23 04:44:30 +0000744 if ':' in url:
745 repl = '<A HREF="%s">%s</A>' % (url, url)
746 else:
747 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
748 list.append(repl)
749 i = i + len(url)
750 j = len(text)
751 list.append(cgi.escape(text[i:j]))
752 return string.join(list, '')
753
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000754 emphasize_prog = None
755
756 def emphasize(self, line):
757 import regsub
758 if not self.emphasize_prog:
759 import regex
760 pat = "\*\([a-zA-Z]+\)\*"
761 self.emphasize_prog = prog = regex.compile(pat)
762 else:
763 prog = self.emphasize_prog
764 return regsub.gsub(prog, "<I>\\1</I>", line)
765
Guido van Rossumaf5be951997-05-22 16:57:50 +0000766print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000767dt = 0
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000768wanttime = 1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000769try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000770 import time
771 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000772 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000773 x = FAQServer()
774 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000775 t2 = time.time()
776 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000777except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000778 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000779 cgi.print_exception()
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000780if wanttime:
781 print "<BR>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000782
783# The following bootstrap script must be placed in cgi-bin/faq.py:
784BOOTSTRAP = """
785#! /usr/local/bin/python
786FAQDIR = "/usr/people/guido/python/FAQ"
787import os, sys
788os.chdir(FAQDIR)
789sys.path.insert(0, os.curdir)
790import faqmain
791"""