blob: 2ce723dfe0dc39e4b6bf2331e12e983dd8ca878a [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 Rossumf701bf11997-05-21 22:25:56 +000010- customize rcs command pathnames
Guido van Rossumd7bfa801997-05-21 21:31:39 +000011- explanation of editing somewhere
12- various embellishments, GIFs, crosslinks, hints, etc.
13- create new sections
14- rearrange entries
15- delete entries
Guido van Rossumd7bfa801997-05-21 21:31:39 +000016- send email on changes
17- optional staging of entries until reviewed?
Guido van Rossumd7bfa801997-05-21 21:31:39 +000018- freeze entries
19- username/password for editors
20- Change references to other Q's and whole sections
Guido van Rossumd7bfa801997-05-21 21:31:39 +000021- support adding annotations, too
Guido van Rossumed531fd1997-05-22 15:21:57 +000022- make it more generic (so you can create your own FAQ)
Guido van Rossumc6447521997-05-23 00:50:01 +000023- more OO structure, e.g. add a class representing one FAQ entry
Guido van Rossumd7bfa801997-05-21 21:31:39 +000024
25"""
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000026
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000027NAMEPAT = "faq??.???.htp"
Guido van Rossumd7bfa801997-05-21 21:31:39 +000028NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000029
Guido van Rossumd1c1ec81997-05-23 17:45:04 +000030# Like so many other things, this should come from a file.
Guido van Rossum4888c7e1997-05-23 15:55:19 +000031SECTIONS = {
32 "1": "General information and availability",
33 "2": "Python in the real world",
34 "3": "Building Python and Other Known Bugs",
35 "4": "Programming in Python",
36 "5": "Extending Python",
37 "6": "Python's design",
38 "7": "Using Python on non-UNIX platforms",
39}
40
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000041class FAQServer:
42
43 def __init__(self):
44 pass
45
46 def main(self):
47 self.form = cgi.FieldStorage()
48 req = self.req or 'frontpage'
49 try:
50 method = getattr(self, 'do_%s' % req)
51 except AttributeError:
52 print "Unrecognized request type", req
53 else:
54 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000055 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000056
Guido van Rossum3c3354c1997-05-21 16:52:18 +000057 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000058 'author', 'email', 'log', 'section', 'number', 'add',
Guido van Rossum4888c7e1997-05-23 15:55:19 +000059 'version', 'edit']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000060
61 def __getattr__(self, key):
62 if key not in self.KEYS:
63 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000064 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000065 form = self.form
66 try:
67 item = form[key]
68 except TypeError, msg:
69 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000070 except KeyError:
71 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000072 value = self.form[key].value
73 value = string.strip(value)
74 setattr(self, key, value)
75 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000076
77 def do_frontpage(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000078 self.prologue("Python FAQ (alpha) Front Page")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000079 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000080 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000081 <LI><A HREF="faq.py?req=index">FAQ index</A>
82 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +000083 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000084 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
85 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +000086 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000087 </UL>
88
Guido van Rossumd1c1ec81997-05-23 17:45:04 +000089 <HR>
90
Guido van Rossumd7bfa801997-05-21 21:31:39 +000091 <H2>Search the FAQ</H2>
92
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000093 <FORM ACTION="faq.py?req=query">
94 <INPUT TYPE=text NAME=query>
Guido van Rossumd1c1ec81997-05-23 17:45:04 +000095 <INPUT TYPE=submit VALUE="Search"><BR>
96 (Case insensitive regular expressions.)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000097 <INPUT TYPE=hidden NAME=req VALUE=query>
98 </FORM>
Guido van Rossumd1c1ec81997-05-23 17:45:04 +000099 <HR>
100 <P>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000101 Disclaimer: these pages are intended to be edited by anyone.
102 Please exercise discretion when editing, don't be rude, etc.
103 """
104
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000105 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000106 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000107 names = os.listdir(os.curdir)
108 names.sort()
109 section = None
110 for name in names:
111 headers, text = self.read(name)
112 if headers:
113 title = headers['title']
114 i = string.find(title, '.')
115 nsec = title[:i]
116 if nsec != section:
117 if section:
118 print """
119 <P>
120 <LI><A HREF="faq.py?req=add&amp;section=%s"
121 >Add new entry</A> (at this point)
122 </UL>
123 """ % section
124 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000125 if SECTIONS.has_key(section):
126 stitle = SECTIONS[section]
127 else:
128 stitle = ""
129 print "<H2>Section %s. %s</H2>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000130 print "<UL>"
131 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
132 name, cgi.escape(title))
133 if section:
134 print """
135 <P>
136 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
137 (at this point)
138 </UL>
139 """ % section
140 else:
141 print "No FAQ entries?!?!"
142
143 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000144 self.prologue("Python FAQ Entry")
145 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000146 name = self.name
147 headers, text = self.read(name)
148 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000149 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000150 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000151 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000152
153 def do_all(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000154 self.prologue("The Whole Python FAQ")
155 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000156 names = os.listdir(os.curdir)
157 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000158 section = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000159 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000160 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000161 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000162 title = headers['title']
163 i = string.find(title, '.')
164 nsec = title[:i]
165 if nsec != section:
166 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000167 if SECTIONS.has_key(section):
168 stitle = SECTIONS[section]
169 else:
170 stitle = ""
171 print "<H1>Section %s. %s</H1>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000172 print "<HR>"
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000173 self.show(name, title, text, edit=(self.edit != 'no'))
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000174 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000175 print "No FAQ entries?!?!"
176
177 def do_roulette(self):
178 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000179 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000180 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000181 Please check the correctness of the entry below.
182 If you find any problems, please edit the entry.
183 <P>
184 <HR>
185 """
186 names = os.listdir(os.curdir)
187 while names:
188 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000189 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000190 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000191 self.show(name, headers['title'], text)
192 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000193 break
194 else:
195 names.remove(name)
196 else:
197 print "No FAQ entries?!?!"
198
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000199 def do_recent(self):
200 import fnmatch, stat
201 names = os.listdir(os.curdir)
202 now = time.time()
203 list = []
204 for name in names:
205 if not fnmatch.fnmatch(name, NAMEPAT):
206 continue
207 try:
208 st = os.stat(name)
209 except os.error:
210 continue
211 tuple = (st[stat.ST_MTIME], name)
212 list.append(tuple)
213 list.sort()
214 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000215 self.prologue("Python FAQ, Most Recently Modified First")
216 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000217 n = 0
218 for (mtime, name) in list:
219 headers, text = self.read(name)
Guido van Rossumc6447521997-05-23 00:50:01 +0000220 if headers and headers.has_key('last-changed-date'):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000221 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000222 n = n+1
223 if not n:
224 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000225
226 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000227 query = self.query
228 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000229 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000230 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000231 import regex
232 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000233 p = regex.compile(query, regex.casefold)
234 names = os.listdir(os.curdir)
235 names.sort()
236 print "<HR>"
237 n = 0
238 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000239 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000240 if headers:
241 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000242 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000243 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000244 n = n+1
245 if not n:
246 print "No hits."
247
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000248 def do_add(self):
249 section = self.section
250 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000251 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000252 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000253 Go to the <A HREF="faq.py?req=index">FAQ index</A>
254 and click on the "Add new entry" link at the end
255 of the section to which you want to add the entry.
256 """
257 return
258 try:
259 nsec = string.atoi(section)
260 except ValueError:
261 print "Bad section number", nsec
262 names = os.listdir(os.curdir)
263 max = 0
264 import regex
265 prog = regex.compile(NAMEREG)
266 for name in names:
267 if prog.match(name) >= 0:
268 s1, s2 = prog.group(1, 2)
269 n1, n2 = string.atoi(s1), string.atoi(s2)
270 if n1 == nsec:
271 if n2 > max:
272 max = n2
273 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000274 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000275 return
276 num = max+1
277 name = "faq%02d.%03d.htp" % (nsec, num)
278 self.name = name
279 self.add = "yes"
280 self.number = str(num)
281 self.do_edit()
282
Guido van Rossumaf5be951997-05-22 16:57:50 +0000283 def do_delete(self):
284 self.prologue("How to delete a FAQ entry")
285 print """
286 At the moment, there's no direct way to delete entries.
287 This is because the entry numbers are also their
288 unique identifiers -- it's a bad idea to renumber entries.
289 <P>
290 If you really think an entry needs to be deleted,
291 change the title to "(deleted)" and make the body
292 empty (keep the entry number in the title though).
293 """
294
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000295 def do_edit(self):
296 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000297 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000298 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000299 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000300 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000301 self.prologue("Python FAQ Edit Form")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000302 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000303 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000304 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000305 self.showedit(name, title, text)
306 if self.add:
307 print """
308 <INPUT TYPE=hidden NAME=add VALUE=%s>
309 <INPUT TYPE=hidden NAME=section VALUE=%s>
310 <INPUT TYPE=hidden NAME=number VALUE=%s>
311 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000312 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000313 <INPUT TYPE=submit VALUE="Review Edit">
314 <INPUT TYPE=hidden NAME=req VALUE=review>
315 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000316 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000317 </FORM>
318 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000319 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000320 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000321
322 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000323 if self.commit:
324 self.checkin()
325 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000326 name = self.name
327 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000328 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000329 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000330 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000331 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000332 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000333 if self.author and '@' in self.email:
334 self.set_cookie(self.author, self.email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000335 self.prologue("Python FAQ Review Form")
336 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000337 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000338 print "<FORM METHOD=POST ACTION=faq.py>"
339 if self.log and self.author and '@' in self.email:
340 print """
341 <INPUT TYPE=submit NAME=commit VALUE="Commit">
342 Click this button to commit the change.
343 <P>
344 <HR>
345 <P>
346 """
347 else:
348 print """
349 To commit this change, please enter your name,
350 email and a log message in the form below.
351 <P>
352 <HR>
353 <P>
354 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000355 self.showedit(name, title, text)
356 if self.add:
357 print """
358 <INPUT TYPE=hidden NAME=add VALUE=%s>
359 <INPUT TYPE=hidden NAME=section VALUE=%s>
360 <INPUT TYPE=hidden NAME=number VALUE=%s>
361 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000362 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000363 <BR>
364 <INPUT TYPE=submit VALUE="Review Edit">
365 <INPUT TYPE=hidden NAME=req VALUE=review>
366 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000367 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000368 </FORM>
369 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000370 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000371
Guido van Rossumf701bf11997-05-21 22:25:56 +0000372 def do_info(self):
373 name = self.name
374 headers, text = self.read(name)
375 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000376 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000377 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000378 self.prologue("Info for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000379 print '<PRE>'
380 sys.stdout.flush()
381 os.system("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" % self.name)
382 print '</PRE>'
383 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
384
385 def do_rlog(self):
386 name = self.name
387 headers, text = self.read(name)
388 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000389 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000390 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000391 self.prologue("RCS log for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000392 print '<PRE>'
393 sys.stdout.flush()
394 os.system("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
395 print '</PRE>'
396
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000397 def checkin(self):
398 import regsub, time, tempfile
399 name = self.name
400
401 headers, oldtext = self.read(name)
402 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000403 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000404 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000405 version = self.version
406 curversion = self.getversion(name)
407 if version != curversion:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000408 self.error("Version conflict.",
409 "You edited version %s but current version is %s." % (
410 version, curversion),
411 '<A HREF="faq.py?req=show&name=%s">Reload.</A>' % name)
Guido van Rossum74427e51997-05-21 23:43:39 +0000412 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000413 text = self.text
414 title = self.title
415 author = self.author
416 email = self.email
417 log = self.log
418 text = regsub.gsub("\r\n", "\n", text)
419 log = regsub.gsub("\r\n", "\n", log)
420 author = string.join(string.split(author))
421 email = string.join(string.split(email))
422 title = string.join(string.split(title))
423 oldtitle = headers['title']
424 oldtitle = string.join(string.split(oldtitle))
425 text = string.strip(text)
426 oldtext = string.strip(oldtext)
427 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000428 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000429 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000430 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000431 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000432 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000433 return
434 remhost = os.environ["REMOTE_HOST"]
435 remaddr = os.environ["REMOTE_ADDR"]
436 try:
437 os.unlink(name + "~")
438 except os.error:
439 pass
440 try:
441 os.rename(name, name + "~")
442 except os.error:
443 pass
444 try:
445 os.unlink(name)
446 except os.error:
447 pass
448 try:
449 f = open(name, "w")
450 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000451 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000452 return
453 now = time.ctime(time.time())
454 f.write("Title: %s\n" % title)
455 f.write("Last-Changed-Date: %s\n" % now)
456 f.write("Last-Changed-Author: %s\n" % author)
457 f.write("Last-Changed-Email: %s\n" % email)
458 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
459 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
460 keys = headers.keys()
461 keys.sort()
462 keys.remove('title')
463 for key in keys:
464 if key[:13] != 'last-changed-':
465 f.write("%s: %s\n" % (string.capwords(key, '-'),
466 headers[key]))
467 f.write("\n")
468 f.write(text)
469 f.write("\n")
470 f.close()
471
472 tfn = tempfile.mktemp()
473 f = open(tfn, "w")
474 f.write("Last-Changed-Date: %s\n" % now)
475 f.write("Last-Changed-Author: %s\n" % author)
476 f.write("Last-Changed-Email: %s\n" % email)
477 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
478 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
479 f.write("\n")
480 f.write(log)
481 f.write("\n")
482 f.close()
483
484 p = os.popen("""
485 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
486 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
487 rm -f %s
488 """ % (name, name, tfn, tfn))
489 output = p.read()
490 sts = p.close()
491 if not sts:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000492 self.set_cookie(author, email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000493 self.prologue("Python FAQ Entry Edited")
494 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000495 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000496 if output:
497 print "<PRE>%s</PRE>" % cgi.escape(output)
498 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000499 self.error("Python FAQ Entry Commit Failed",
500 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000501 if output:
502 print "<PRE>%s</PRE>" % cgi.escape(output)
Guido van Rossum64099e91997-05-22 15:49:23 +0000503 print '<HR>'
504 print '<A HREF="faq.py?req=show&name=%s">Reload this entry.</A>' % name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000505
Guido van Rossumaf5be951997-05-22 16:57:50 +0000506 def set_cookie(self, author, email):
507 name = "Python-FAQ-ID"
508 value = "%s;%s" % (author, email)
509 import urllib
510 value = urllib.quote(value)
511 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
512 print "domain=%s;" % os.environ['HTTP_HOST'],
513 print "expires=Sat, 01-Jan-2000 00:00:00 GMT"
514
515 def get_cookie(self):
516 if not os.environ.has_key('HTTP_COOKIE'):
517 return "", ""
518 raw = os.environ['HTTP_COOKIE']
519 words = string.split(raw, ';')
520 cookies = {}
521 for word in words:
522 i = string.find(word, '=')
523 if i >= 0:
524 key, value = word[:i], word[i+1:]
525 cookies[key] = value
526 if not cookies.has_key('Python-FAQ-ID'):
527 return "", ""
528 value = cookies['Python-FAQ-ID']
529 import urllib
530 value = urllib.unquote(value)
531 i = string.rfind(value, ';')
532 author, email = value[:i], value[i+1:]
533 return author, email
534
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000535 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000536 author = self.author
537 email = self.email
538 if not author or not email:
539 a, e = self.get_cookie()
540 author = author or a
541 email = email or e
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000542 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000543 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000544 <TEXTAREA COLS=80 ROWS=20 NAME=text>""" % title
545 print cgi.escape(string.strip(text))
546 print """</TEXTAREA>
547 <BR>
548 Please provide the following information for logging purposes:
549 <BR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000550 <CODE>Name : </CODE><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
551 <BR>
552 <CODE>Email: </CODE><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
553 <BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000554 Log message (reason for the change):<BR>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000555 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA>
556 """ % (author, email, self.log)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000557
558 def showheaders(self, headers):
559 print "<UL>"
560 keys = map(string.lower, headers.keys())
561 keys.sort()
562 for key in keys:
563 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
564 headers[key] or '')
565 print "</UL>"
566
Guido van Rossumed531fd1997-05-22 15:21:57 +0000567 headers = None
568
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000569 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000570 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000571 import fnmatch, rfc822
572 if not fnmatch.fnmatch(name, NAMEPAT):
573 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000574 if self.add:
575 try:
576 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
577 string.atoi(self.number))
578 except ValueError:
579 return None, None
580 if fname != name:
581 return None, None
582 headers = {'title': "%s.%s. " % (self.section, self.number)}
583 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000584 else:
585 f = open(name)
586 headers = rfc822.Message(f)
587 text = f.read()
588 f.close()
589 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000590 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000591
Guido van Rossumed531fd1997-05-22 15:21:57 +0000592 def show(self, name, title, text, edit=1):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000593 # XXX Should put <A> tags around recognizable URLs
594 # XXX Should also turn "see section N" into hyperlinks
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000595 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000596 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000597 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000598 if not string.strip(line):
599 if pre:
600 print '</PRE>'
601 pre = 0
602 else:
603 print '<P>'
604 else:
Guido van Rossum5527db51997-05-23 04:44:30 +0000605 if line[0] not in string.whitespace:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000606 if pre:
607 print '</PRE>'
608 pre = 0
609 else:
610 if not pre:
611 print '<PRE>'
612 pre = 1
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000613 if '/' in line or '@' in line:
614 line = self.translate(line)
615 if not pre and '*' in line:
616 line = self.emphasize(line)
617 print line
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000618 if pre:
619 print '</PRE>'
620 pre = 0
621 print '<P>'
622 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000623 print """
624 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
625 <A HREF="faq.py?req=info&name=%s" TARGET=_blank>Log info</A>
626 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000627 if self.headers:
628 try:
629 date = self.headers['last-changed-date']
630 author = self.headers['last-changed-author']
631 email = self.headers['last-changed-email']
632 except KeyError:
633 pass
634 else:
Guido van Rossum64099e91997-05-22 15:49:23 +0000635 s = '/ Last changed on %s by <A HREF="%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000636 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000637 print '<P>'
638 print "<HR>"
639
Guido van Rossum74427e51997-05-21 23:43:39 +0000640 def getversion(self, name):
641 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000642 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000643 while 1:
644 line = p.readline()
645 if not line:
646 break
647 if line[:5] == 'head:':
648 head = string.strip(line[5:])
649 p.close()
650 return head
651
652 def prologue(self, title):
653 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000654 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000655 <HTML>
656 <HEAD>
657 <TITLE>%s</TITLE>
658 </HEAD>
659 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
660 BGCOLOR="#FFFFFF"
661 TEXT="#000000"
662 LINK="#AA0000"
663 VLINK="#906A6A">
664 <H1>%s</H1>
665 ''' % (title, title)
666
Guido van Rossumaf5be951997-05-22 16:57:50 +0000667 def error(self, *messages):
668 self.prologue("Python FAQ error")
669 print "Sorry, an error occurred:<BR>"
670 for message in messages:
671 print message,
672 print
673
Guido van Rossum74427e51997-05-21 23:43:39 +0000674 def epilogue(self):
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000675 if self.edit == 'no':
676 global wanttime
677 wanttime = 0
678 else:
679 print '''
680 <P>
681 <HR>
682 <A HREF="http://www.python.org">Python home</A> /
683 <A HREF="faq.py">FAQ home</A> /
684 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
685 '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000686 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000687 </BODY>
688 </HTML>
689 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000690
Guido van Rossum5527db51997-05-23 04:44:30 +0000691 translate_prog = None
692
693 def translate(self, text):
694 if not self.translate_prog:
695 import regex
696 url = '\(http\|ftp\)://[^ \t\r\n]*'
697 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
698 self.translate_prog = prog = regex.compile(url + "\|" + email)
699 else:
700 prog = self.translate_prog
701 i = 0
702 list = []
703 while 1:
704 j = prog.search(text, i)
705 if j < 0:
706 break
707 list.append(cgi.escape(text[i:j]))
708 i = j
709 url = prog.group(0)
710 while url[-1] in ");:,.?":
711 url = url[:-1]
712 url = cgi.escape(url)
713 if ':' in url:
714 repl = '<A HREF="%s">%s</A>' % (url, url)
715 else:
716 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
717 list.append(repl)
718 i = i + len(url)
719 j = len(text)
720 list.append(cgi.escape(text[i:j]))
721 return string.join(list, '')
722
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000723 emphasize_prog = None
724
725 def emphasize(self, line):
726 import regsub
727 if not self.emphasize_prog:
728 import regex
729 pat = "\*\([a-zA-Z]+\)\*"
730 self.emphasize_prog = prog = regex.compile(pat)
731 else:
732 prog = self.emphasize_prog
733 return regsub.gsub(prog, "<I>\\1</I>", line)
734
Guido van Rossumaf5be951997-05-22 16:57:50 +0000735print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000736dt = 0
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000737wanttime = 1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000738try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000739 import time
740 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000741 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000742 x = FAQServer()
743 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000744 t2 = time.time()
745 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000746except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000747 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000748 cgi.print_exception()
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000749if wanttime:
750 print "<BR>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000751
752# The following bootstrap script must be placed in cgi-bin/faq.py:
753BOOTSTRAP = """
754#! /usr/local/bin/python
755FAQDIR = "/usr/people/guido/python/FAQ"
756import os, sys
757os.chdir(FAQDIR)
758sys.path.insert(0, os.curdir)
759import faqmain
760"""