blob: d105d2c7612e5f9bb0ae1c6466c9397928cfc28c [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
Guido van Rossumd7bfa801997-05-21 21:31:39 +000012- explanation of editing somewhere
13- various embellishments, GIFs, crosslinks, hints, etc.
14- create new sections
15- rearrange entries
16- delete entries
Guido van Rossumd7bfa801997-05-21 21:31:39 +000017- send email on changes
18- optional staging of entries until reviewed?
Guido van Rossumd7bfa801997-05-21 21:31:39 +000019- freeze entries
20- username/password for editors
21- Change references to other Q's and whole sections
Guido van Rossumd7bfa801997-05-21 21:31:39 +000022- support adding annotations, too
Guido van Rossumed531fd1997-05-22 15:21:57 +000023- make it more generic (so you can create your own FAQ)
Guido van Rossumc6447521997-05-23 00:50:01 +000024- more OO structure, e.g. add a class representing one FAQ entry
Guido van Rossumd7bfa801997-05-21 21:31:39 +000025
26"""
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000027
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000028NAMEPAT = "faq??.???.htp"
Guido van Rossumd7bfa801997-05-21 21:31:39 +000029NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000030
31class FAQServer:
32
33 def __init__(self):
34 pass
35
36 def main(self):
37 self.form = cgi.FieldStorage()
38 req = self.req or 'frontpage'
39 try:
40 method = getattr(self, 'do_%s' % req)
41 except AttributeError:
42 print "Unrecognized request type", req
43 else:
44 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000045 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000046
Guido van Rossum3c3354c1997-05-21 16:52:18 +000047 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000048 'author', 'email', 'log', 'section', 'number', 'add',
49 'version']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000050
51 def __getattr__(self, key):
52 if key not in self.KEYS:
53 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000054 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000055 form = self.form
56 try:
57 item = form[key]
58 except TypeError, msg:
59 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000060 except KeyError:
61 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000062 value = self.form[key].value
63 value = string.strip(value)
64 setattr(self, key, value)
65 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000066
67 def do_frontpage(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000068 self.prologue("Python FAQ (alpha) Front Page")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000069 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000070 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000071 <LI><A HREF="faq.py?req=index">FAQ index</A>
72 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +000073 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000074 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
75 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +000076 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000077 </UL>
78
Guido van Rossumd7bfa801997-05-21 21:31:39 +000079 <H2>Search the FAQ</H2>
80
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000081 <FORM ACTION="faq.py?req=query">
82 <INPUT TYPE=text NAME=query>
83 <INPUT TYPE=submit VALUE="Search">
84 <INPUT TYPE=hidden NAME=req VALUE=query>
85 </FORM>
86
87 Disclaimer: these pages are intended to be edited by anyone.
88 Please exercise discretion when editing, don't be rude, etc.
89 """
90
Guido van Rossumd7bfa801997-05-21 21:31:39 +000091 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000092 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +000093 names = os.listdir(os.curdir)
94 names.sort()
95 section = None
96 for name in names:
97 headers, text = self.read(name)
98 if headers:
99 title = headers['title']
100 i = string.find(title, '.')
101 nsec = title[:i]
102 if nsec != section:
103 if section:
104 print """
105 <P>
106 <LI><A HREF="faq.py?req=add&amp;section=%s"
107 >Add new entry</A> (at this point)
108 </UL>
109 """ % section
110 section = nsec
111 print "<H2>Section %s</H2>" % section
112 print "<UL>"
113 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
114 name, cgi.escape(title))
115 if section:
116 print """
117 <P>
118 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
119 (at this point)
120 </UL>
121 """ % section
122 else:
123 print "No FAQ entries?!?!"
124
125 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000126 self.prologue("Python FAQ Entry")
127 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000128 name = self.name
129 headers, text = self.read(name)
130 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000131 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000132 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000133 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000134
135 def do_all(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000136 self.prologue("The Whole Python FAQ")
137 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000138 names = os.listdir(os.curdir)
139 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000140 section = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000141 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000142 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000143 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000144 title = headers['title']
145 i = string.find(title, '.')
146 nsec = title[:i]
147 if nsec != section:
148 section = nsec
149 print "<H1>Section %s</H1>" % section
150 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000151 self.show(name, title, text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000152 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000153 print "No FAQ entries?!?!"
154
155 def do_roulette(self):
156 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000157 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000158 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000159 Please check the correctness of the entry below.
160 If you find any problems, please edit the entry.
161 <P>
162 <HR>
163 """
164 names = os.listdir(os.curdir)
165 while names:
166 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000167 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000168 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000169 self.show(name, headers['title'], text)
170 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000171 break
172 else:
173 names.remove(name)
174 else:
175 print "No FAQ entries?!?!"
176
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000177 def do_recent(self):
178 import fnmatch, stat
179 names = os.listdir(os.curdir)
180 now = time.time()
181 list = []
182 for name in names:
183 if not fnmatch.fnmatch(name, NAMEPAT):
184 continue
185 try:
186 st = os.stat(name)
187 except os.error:
188 continue
189 tuple = (st[stat.ST_MTIME], name)
190 list.append(tuple)
191 list.sort()
192 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000193 self.prologue("Python FAQ, Most Recently Modified First")
194 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000195 n = 0
196 for (mtime, name) in list:
197 headers, text = self.read(name)
Guido van Rossumc6447521997-05-23 00:50:01 +0000198 if headers and headers.has_key('last-changed-date'):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000199 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000200 n = n+1
201 if not n:
202 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000203
204 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000205 query = self.query
206 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000207 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000208 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000209 import regex
210 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000211 p = regex.compile(query, regex.casefold)
212 names = os.listdir(os.curdir)
213 names.sort()
214 print "<HR>"
215 n = 0
216 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000217 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000218 if headers:
219 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000220 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000221 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000222 n = n+1
223 if not n:
224 print "No hits."
225
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000226 def do_add(self):
227 section = self.section
228 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000229 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000230 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000231 Go to the <A HREF="faq.py?req=index">FAQ index</A>
232 and click on the "Add new entry" link at the end
233 of the section to which you want to add the entry.
234 """
235 return
236 try:
237 nsec = string.atoi(section)
238 except ValueError:
239 print "Bad section number", nsec
240 names = os.listdir(os.curdir)
241 max = 0
242 import regex
243 prog = regex.compile(NAMEREG)
244 for name in names:
245 if prog.match(name) >= 0:
246 s1, s2 = prog.group(1, 2)
247 n1, n2 = string.atoi(s1), string.atoi(s2)
248 if n1 == nsec:
249 if n2 > max:
250 max = n2
251 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000252 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000253 return
254 num = max+1
255 name = "faq%02d.%03d.htp" % (nsec, num)
256 self.name = name
257 self.add = "yes"
258 self.number = str(num)
259 self.do_edit()
260
Guido van Rossumaf5be951997-05-22 16:57:50 +0000261 def do_delete(self):
262 self.prologue("How to delete a FAQ entry")
263 print """
264 At the moment, there's no direct way to delete entries.
265 This is because the entry numbers are also their
266 unique identifiers -- it's a bad idea to renumber entries.
267 <P>
268 If you really think an entry needs to be deleted,
269 change the title to "(deleted)" and make the body
270 empty (keep the entry number in the title though).
271 """
272
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000273 def do_edit(self):
274 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000275 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000276 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000277 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000278 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000279 self.prologue("Python FAQ Edit Form")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000280 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000281 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000282 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000283 self.showedit(name, title, text)
284 if self.add:
285 print """
286 <INPUT TYPE=hidden NAME=add VALUE=%s>
287 <INPUT TYPE=hidden NAME=section VALUE=%s>
288 <INPUT TYPE=hidden NAME=number VALUE=%s>
289 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000290 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000291 <INPUT TYPE=submit VALUE="Review Edit">
292 <INPUT TYPE=hidden NAME=req VALUE=review>
293 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000294 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000295 </FORM>
296 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000297 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000298 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000299
300 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000301 if self.commit:
302 self.checkin()
303 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000304 name = self.name
305 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000306 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000307 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000308 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000309 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000310 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000311 if self.author and '@' in self.email:
312 self.set_cookie(self.author, self.email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000313 self.prologue("Python FAQ Review Form")
314 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000315 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000316 print "<FORM METHOD=POST ACTION=faq.py>"
317 if self.log and self.author and '@' in self.email:
318 print """
319 <INPUT TYPE=submit NAME=commit VALUE="Commit">
320 Click this button to commit the change.
321 <P>
322 <HR>
323 <P>
324 """
325 else:
326 print """
327 To commit this change, please enter your name,
328 email and a log message in the form below.
329 <P>
330 <HR>
331 <P>
332 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000333 self.showedit(name, title, text)
334 if self.add:
335 print """
336 <INPUT TYPE=hidden NAME=add VALUE=%s>
337 <INPUT TYPE=hidden NAME=section VALUE=%s>
338 <INPUT TYPE=hidden NAME=number VALUE=%s>
339 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000340 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000341 <BR>
342 <INPUT TYPE=submit VALUE="Review Edit">
343 <INPUT TYPE=hidden NAME=req VALUE=review>
344 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000345 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000346 </FORM>
347 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000348 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000349
Guido van Rossumf701bf11997-05-21 22:25:56 +0000350 def do_info(self):
351 name = self.name
352 headers, text = self.read(name)
353 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000354 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000355 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000356 self.prologue("Info for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000357 print '<PRE>'
358 sys.stdout.flush()
359 os.system("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" % self.name)
360 print '</PRE>'
361 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
362
363 def do_rlog(self):
364 name = self.name
365 headers, text = self.read(name)
366 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000367 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000368 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000369 self.prologue("RCS log for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000370 print '<PRE>'
371 sys.stdout.flush()
372 os.system("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
373 print '</PRE>'
374
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000375 def checkin(self):
376 import regsub, time, tempfile
377 name = self.name
378
379 headers, oldtext = self.read(name)
380 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000381 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000382 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000383 version = self.version
384 curversion = self.getversion(name)
385 if version != curversion:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000386 self.error("Version conflict.",
387 "You edited version %s but current version is %s." % (
388 version, curversion),
389 '<A HREF="faq.py?req=show&name=%s">Reload.</A>' % name)
Guido van Rossum74427e51997-05-21 23:43:39 +0000390 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000391 text = self.text
392 title = self.title
393 author = self.author
394 email = self.email
395 log = self.log
396 text = regsub.gsub("\r\n", "\n", text)
397 log = regsub.gsub("\r\n", "\n", log)
398 author = string.join(string.split(author))
399 email = string.join(string.split(email))
400 title = string.join(string.split(title))
401 oldtitle = headers['title']
402 oldtitle = string.join(string.split(oldtitle))
403 text = string.strip(text)
404 oldtext = string.strip(oldtext)
405 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000406 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000407 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000408 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000409 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000410 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000411 return
412 remhost = os.environ["REMOTE_HOST"]
413 remaddr = os.environ["REMOTE_ADDR"]
414 try:
415 os.unlink(name + "~")
416 except os.error:
417 pass
418 try:
419 os.rename(name, name + "~")
420 except os.error:
421 pass
422 try:
423 os.unlink(name)
424 except os.error:
425 pass
426 try:
427 f = open(name, "w")
428 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000429 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000430 return
431 now = time.ctime(time.time())
432 f.write("Title: %s\n" % title)
433 f.write("Last-Changed-Date: %s\n" % now)
434 f.write("Last-Changed-Author: %s\n" % author)
435 f.write("Last-Changed-Email: %s\n" % email)
436 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
437 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
438 keys = headers.keys()
439 keys.sort()
440 keys.remove('title')
441 for key in keys:
442 if key[:13] != 'last-changed-':
443 f.write("%s: %s\n" % (string.capwords(key, '-'),
444 headers[key]))
445 f.write("\n")
446 f.write(text)
447 f.write("\n")
448 f.close()
449
450 tfn = tempfile.mktemp()
451 f = open(tfn, "w")
452 f.write("Last-Changed-Date: %s\n" % now)
453 f.write("Last-Changed-Author: %s\n" % author)
454 f.write("Last-Changed-Email: %s\n" % email)
455 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
456 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
457 f.write("\n")
458 f.write(log)
459 f.write("\n")
460 f.close()
461
462 p = os.popen("""
463 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
464 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
465 rm -f %s
466 """ % (name, name, tfn, tfn))
467 output = p.read()
468 sts = p.close()
469 if not sts:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000470 self.set_cookie(author, email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000471 self.prologue("Python FAQ Entry Edited")
472 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000473 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000474 if output:
475 print "<PRE>%s</PRE>" % cgi.escape(output)
476 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000477 self.error("Python FAQ Entry Commit Failed",
478 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000479 if output:
480 print "<PRE>%s</PRE>" % cgi.escape(output)
Guido van Rossum64099e91997-05-22 15:49:23 +0000481 print '<HR>'
482 print '<A HREF="faq.py?req=show&name=%s">Reload this entry.</A>' % name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000483
Guido van Rossumaf5be951997-05-22 16:57:50 +0000484 def set_cookie(self, author, email):
485 name = "Python-FAQ-ID"
486 value = "%s;%s" % (author, email)
487 import urllib
488 value = urllib.quote(value)
489 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
490 print "domain=%s;" % os.environ['HTTP_HOST'],
491 print "expires=Sat, 01-Jan-2000 00:00:00 GMT"
492
493 def get_cookie(self):
494 if not os.environ.has_key('HTTP_COOKIE'):
495 return "", ""
496 raw = os.environ['HTTP_COOKIE']
497 words = string.split(raw, ';')
498 cookies = {}
499 for word in words:
500 i = string.find(word, '=')
501 if i >= 0:
502 key, value = word[:i], word[i+1:]
503 cookies[key] = value
504 if not cookies.has_key('Python-FAQ-ID'):
505 return "", ""
506 value = cookies['Python-FAQ-ID']
507 import urllib
508 value = urllib.unquote(value)
509 i = string.rfind(value, ';')
510 author, email = value[:i], value[i+1:]
511 return author, email
512
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000513 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000514 author = self.author
515 email = self.email
516 if not author or not email:
517 a, e = self.get_cookie()
518 author = author or a
519 email = email or e
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000520 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000521 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000522 <TEXTAREA COLS=80 ROWS=20 NAME=text>""" % title
523 print cgi.escape(string.strip(text))
524 print """</TEXTAREA>
525 <BR>
526 Please provide the following information for logging purposes:
527 <BR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000528 <CODE>Name : </CODE><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
529 <BR>
530 <CODE>Email: </CODE><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
531 <BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000532 Log message (reason for the change):<BR>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000533 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA>
534 """ % (author, email, self.log)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000535
536 def showheaders(self, headers):
537 print "<UL>"
538 keys = map(string.lower, headers.keys())
539 keys.sort()
540 for key in keys:
541 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
542 headers[key] or '')
543 print "</UL>"
544
Guido van Rossumed531fd1997-05-22 15:21:57 +0000545 headers = None
546
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000547 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000548 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000549 import fnmatch, rfc822
550 if not fnmatch.fnmatch(name, NAMEPAT):
551 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000552 if self.add:
553 try:
554 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
555 string.atoi(self.number))
556 except ValueError:
557 return None, None
558 if fname != name:
559 return None, None
560 headers = {'title': "%s.%s. " % (self.section, self.number)}
561 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000562 else:
563 f = open(name)
564 headers = rfc822.Message(f)
565 text = f.read()
566 f.close()
567 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000568 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000569
Guido van Rossumed531fd1997-05-22 15:21:57 +0000570 def show(self, name, title, text, edit=1):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000571 # XXX Should put <A> tags around recognizable URLs
572 # XXX Should also turn "see section N" into hyperlinks
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000573 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000574 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000575 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000576 if not string.strip(line):
577 if pre:
578 print '</PRE>'
579 pre = 0
580 else:
581 print '<P>'
582 else:
Guido van Rossum5527db51997-05-23 04:44:30 +0000583 if line[0] not in string.whitespace:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000584 if pre:
585 print '</PRE>'
586 pre = 0
587 else:
588 if not pre:
589 print '<PRE>'
590 pre = 1
Guido van Rossum5527db51997-05-23 04:44:30 +0000591 print self.translate(line)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000592 if pre:
593 print '</PRE>'
594 pre = 0
595 print '<P>'
596 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000597 print """
598 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
599 <A HREF="faq.py?req=info&name=%s" TARGET=_blank>Log info</A>
600 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000601 if self.headers:
602 try:
603 date = self.headers['last-changed-date']
604 author = self.headers['last-changed-author']
605 email = self.headers['last-changed-email']
606 except KeyError:
607 pass
608 else:
Guido van Rossum64099e91997-05-22 15:49:23 +0000609 s = '/ Last changed on %s by <A HREF="%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000610 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000611 print '<P>'
612 print "<HR>"
613
Guido van Rossum74427e51997-05-21 23:43:39 +0000614 def getversion(self, name):
615 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000616 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000617 while 1:
618 line = p.readline()
619 if not line:
620 break
621 if line[:5] == 'head:':
622 head = string.strip(line[5:])
623 p.close()
624 return head
625
626 def prologue(self, title):
627 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000628 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000629 <HTML>
630 <HEAD>
631 <TITLE>%s</TITLE>
632 </HEAD>
633 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
634 BGCOLOR="#FFFFFF"
635 TEXT="#000000"
636 LINK="#AA0000"
637 VLINK="#906A6A">
638 <H1>%s</H1>
639 ''' % (title, title)
640
Guido van Rossumaf5be951997-05-22 16:57:50 +0000641 def error(self, *messages):
642 self.prologue("Python FAQ error")
643 print "Sorry, an error occurred:<BR>"
644 for message in messages:
645 print message,
646 print
647
Guido van Rossum74427e51997-05-21 23:43:39 +0000648 def epilogue(self):
649 print '''
650 <P>
651 <HR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000652 <A HREF="http://www.python.org">Python home</A> /
653 <A HREF="faq.py">FAQ home</A> /
Guido van Rossum64099e91997-05-22 15:49:23 +0000654 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
Guido van Rossum74427e51997-05-21 23:43:39 +0000655 </BODY>
656 </HTML>
657 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000658
Guido van Rossum5527db51997-05-23 04:44:30 +0000659 translate_prog = None
660
661 def translate(self, text):
662 if not self.translate_prog:
663 import regex
664 url = '\(http\|ftp\)://[^ \t\r\n]*'
665 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
666 self.translate_prog = prog = regex.compile(url + "\|" + email)
667 else:
668 prog = self.translate_prog
669 i = 0
670 list = []
671 while 1:
672 j = prog.search(text, i)
673 if j < 0:
674 break
675 list.append(cgi.escape(text[i:j]))
676 i = j
677 url = prog.group(0)
678 while url[-1] in ");:,.?":
679 url = url[:-1]
680 url = cgi.escape(url)
681 if ':' in url:
682 repl = '<A HREF="%s">%s</A>' % (url, url)
683 else:
684 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
685 list.append(repl)
686 i = i + len(url)
687 j = len(text)
688 list.append(cgi.escape(text[i:j]))
689 return string.join(list, '')
690
Guido van Rossumaf5be951997-05-22 16:57:50 +0000691print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000692dt = 0
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000693try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000694 import time
695 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000696 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000697 x = FAQServer()
698 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000699 t2 = time.time()
700 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000701except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000702 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000703 cgi.print_exception()
Guido van Rossum74427e51997-05-21 23:43:39 +0000704print "<P>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000705
706# The following bootstrap script must be placed in cgi-bin/faq.py:
707BOOTSTRAP = """
708#! /usr/local/bin/python
709FAQDIR = "/usr/people/guido/python/FAQ"
710import os, sys
711os.chdir(FAQDIR)
712sys.path.insert(0, os.curdir)
713import faqmain
714"""