blob: 82c4657dc3d4163f6cc83ffac665d60291042ecf [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 Rossum4888c7e1997-05-23 15:55:19 +000030SECTIONS = {
31 "1": "General information and availability",
32 "2": "Python in the real world",
33 "3": "Building Python and Other Known Bugs",
34 "4": "Programming in Python",
35 "5": "Extending Python",
36 "6": "Python's design",
37 "7": "Using Python on non-UNIX platforms",
38}
39
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000040class FAQServer:
41
42 def __init__(self):
43 pass
44
45 def main(self):
46 self.form = cgi.FieldStorage()
47 req = self.req or 'frontpage'
48 try:
49 method = getattr(self, 'do_%s' % req)
50 except AttributeError:
51 print "Unrecognized request type", req
52 else:
53 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000054 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000055
Guido van Rossum3c3354c1997-05-21 16:52:18 +000056 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000057 'author', 'email', 'log', 'section', 'number', 'add',
Guido van Rossum4888c7e1997-05-23 15:55:19 +000058 'version', 'edit']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000059
60 def __getattr__(self, key):
61 if key not in self.KEYS:
62 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000063 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000064 form = self.form
65 try:
66 item = form[key]
67 except TypeError, msg:
68 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000069 except KeyError:
70 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000071 value = self.form[key].value
72 value = string.strip(value)
73 setattr(self, key, value)
74 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000075
76 def do_frontpage(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000077 self.prologue("Python FAQ (alpha) Front Page")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000078 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000079 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000080 <LI><A HREF="faq.py?req=index">FAQ index</A>
81 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +000082 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000083 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
84 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +000085 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000086 </UL>
87
Guido van Rossumd7bfa801997-05-21 21:31:39 +000088 <H2>Search the FAQ</H2>
89
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000090 <FORM ACTION="faq.py?req=query">
91 <INPUT TYPE=text NAME=query>
92 <INPUT TYPE=submit VALUE="Search">
93 <INPUT TYPE=hidden NAME=req VALUE=query>
94 </FORM>
95
96 Disclaimer: these pages are intended to be edited by anyone.
97 Please exercise discretion when editing, don't be rude, etc.
98 """
99
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000100 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000101 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000102 names = os.listdir(os.curdir)
103 names.sort()
104 section = None
105 for name in names:
106 headers, text = self.read(name)
107 if headers:
108 title = headers['title']
109 i = string.find(title, '.')
110 nsec = title[:i]
111 if nsec != section:
112 if section:
113 print """
114 <P>
115 <LI><A HREF="faq.py?req=add&amp;section=%s"
116 >Add new entry</A> (at this point)
117 </UL>
118 """ % section
119 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000120 if SECTIONS.has_key(section):
121 stitle = SECTIONS[section]
122 else:
123 stitle = ""
124 print "<H2>Section %s. %s</H2>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000125 print "<UL>"
126 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
127 name, cgi.escape(title))
128 if section:
129 print """
130 <P>
131 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
132 (at this point)
133 </UL>
134 """ % section
135 else:
136 print "No FAQ entries?!?!"
137
138 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000139 self.prologue("Python FAQ Entry")
140 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000141 name = self.name
142 headers, text = self.read(name)
143 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000144 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000145 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000146 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000147
148 def do_all(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000149 self.prologue("The Whole Python FAQ")
150 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000151 names = os.listdir(os.curdir)
152 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000153 section = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000154 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000155 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000156 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000157 title = headers['title']
158 i = string.find(title, '.')
159 nsec = title[:i]
160 if nsec != section:
161 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000162 if SECTIONS.has_key(section):
163 stitle = SECTIONS[section]
164 else:
165 stitle = ""
166 print "<H1>Section %s. %s</H1>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000167 print "<HR>"
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000168 self.show(name, title, text, edit=(self.edit != 'no'))
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000169 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000170 print "No FAQ entries?!?!"
171
172 def do_roulette(self):
173 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000174 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000175 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000176 Please check the correctness of the entry below.
177 If you find any problems, please edit the entry.
178 <P>
179 <HR>
180 """
181 names = os.listdir(os.curdir)
182 while names:
183 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000184 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000185 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000186 self.show(name, headers['title'], text)
187 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000188 break
189 else:
190 names.remove(name)
191 else:
192 print "No FAQ entries?!?!"
193
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000194 def do_recent(self):
195 import fnmatch, stat
196 names = os.listdir(os.curdir)
197 now = time.time()
198 list = []
199 for name in names:
200 if not fnmatch.fnmatch(name, NAMEPAT):
201 continue
202 try:
203 st = os.stat(name)
204 except os.error:
205 continue
206 tuple = (st[stat.ST_MTIME], name)
207 list.append(tuple)
208 list.sort()
209 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000210 self.prologue("Python FAQ, Most Recently Modified First")
211 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000212 n = 0
213 for (mtime, name) in list:
214 headers, text = self.read(name)
Guido van Rossumc6447521997-05-23 00:50:01 +0000215 if headers and headers.has_key('last-changed-date'):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000216 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000217 n = n+1
218 if not n:
219 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000220
221 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000222 query = self.query
223 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000224 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000225 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000226 import regex
227 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000228 p = regex.compile(query, regex.casefold)
229 names = os.listdir(os.curdir)
230 names.sort()
231 print "<HR>"
232 n = 0
233 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000234 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000235 if headers:
236 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000237 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000238 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000239 n = n+1
240 if not n:
241 print "No hits."
242
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000243 def do_add(self):
244 section = self.section
245 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000246 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000247 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000248 Go to the <A HREF="faq.py?req=index">FAQ index</A>
249 and click on the "Add new entry" link at the end
250 of the section to which you want to add the entry.
251 """
252 return
253 try:
254 nsec = string.atoi(section)
255 except ValueError:
256 print "Bad section number", nsec
257 names = os.listdir(os.curdir)
258 max = 0
259 import regex
260 prog = regex.compile(NAMEREG)
261 for name in names:
262 if prog.match(name) >= 0:
263 s1, s2 = prog.group(1, 2)
264 n1, n2 = string.atoi(s1), string.atoi(s2)
265 if n1 == nsec:
266 if n2 > max:
267 max = n2
268 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000269 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000270 return
271 num = max+1
272 name = "faq%02d.%03d.htp" % (nsec, num)
273 self.name = name
274 self.add = "yes"
275 self.number = str(num)
276 self.do_edit()
277
Guido van Rossumaf5be951997-05-22 16:57:50 +0000278 def do_delete(self):
279 self.prologue("How to delete a FAQ entry")
280 print """
281 At the moment, there's no direct way to delete entries.
282 This is because the entry numbers are also their
283 unique identifiers -- it's a bad idea to renumber entries.
284 <P>
285 If you really think an entry needs to be deleted,
286 change the title to "(deleted)" and make the body
287 empty (keep the entry number in the title though).
288 """
289
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000290 def do_edit(self):
291 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000292 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000293 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000294 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000295 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000296 self.prologue("Python FAQ Edit Form")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000297 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000298 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000299 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000300 self.showedit(name, title, text)
301 if self.add:
302 print """
303 <INPUT TYPE=hidden NAME=add VALUE=%s>
304 <INPUT TYPE=hidden NAME=section VALUE=%s>
305 <INPUT TYPE=hidden NAME=number VALUE=%s>
306 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000307 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000308 <INPUT TYPE=submit VALUE="Review Edit">
309 <INPUT TYPE=hidden NAME=req VALUE=review>
310 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000311 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000312 </FORM>
313 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000314 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000315 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000316
317 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000318 if self.commit:
319 self.checkin()
320 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000321 name = self.name
322 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000323 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000324 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000325 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000326 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000327 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000328 if self.author and '@' in self.email:
329 self.set_cookie(self.author, self.email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000330 self.prologue("Python FAQ Review Form")
331 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000332 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000333 print "<FORM METHOD=POST ACTION=faq.py>"
334 if self.log and self.author and '@' in self.email:
335 print """
336 <INPUT TYPE=submit NAME=commit VALUE="Commit">
337 Click this button to commit the change.
338 <P>
339 <HR>
340 <P>
341 """
342 else:
343 print """
344 To commit this change, please enter your name,
345 email and a log message in the form below.
346 <P>
347 <HR>
348 <P>
349 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000350 self.showedit(name, title, text)
351 if self.add:
352 print """
353 <INPUT TYPE=hidden NAME=add VALUE=%s>
354 <INPUT TYPE=hidden NAME=section VALUE=%s>
355 <INPUT TYPE=hidden NAME=number VALUE=%s>
356 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000357 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000358 <BR>
359 <INPUT TYPE=submit VALUE="Review Edit">
360 <INPUT TYPE=hidden NAME=req VALUE=review>
361 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000362 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000363 </FORM>
364 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000365 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000366
Guido van Rossumf701bf11997-05-21 22:25:56 +0000367 def do_info(self):
368 name = self.name
369 headers, text = self.read(name)
370 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000371 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000372 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000373 self.prologue("Info for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000374 print '<PRE>'
375 sys.stdout.flush()
376 os.system("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" % self.name)
377 print '</PRE>'
378 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
379
380 def do_rlog(self):
381 name = self.name
382 headers, text = self.read(name)
383 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000384 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000385 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000386 self.prologue("RCS log for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000387 print '<PRE>'
388 sys.stdout.flush()
389 os.system("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
390 print '</PRE>'
391
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000392 def checkin(self):
393 import regsub, time, tempfile
394 name = self.name
395
396 headers, oldtext = self.read(name)
397 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000398 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000399 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000400 version = self.version
401 curversion = self.getversion(name)
402 if version != curversion:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000403 self.error("Version conflict.",
404 "You edited version %s but current version is %s." % (
405 version, curversion),
406 '<A HREF="faq.py?req=show&name=%s">Reload.</A>' % name)
Guido van Rossum74427e51997-05-21 23:43:39 +0000407 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000408 text = self.text
409 title = self.title
410 author = self.author
411 email = self.email
412 log = self.log
413 text = regsub.gsub("\r\n", "\n", text)
414 log = regsub.gsub("\r\n", "\n", log)
415 author = string.join(string.split(author))
416 email = string.join(string.split(email))
417 title = string.join(string.split(title))
418 oldtitle = headers['title']
419 oldtitle = string.join(string.split(oldtitle))
420 text = string.strip(text)
421 oldtext = string.strip(oldtext)
422 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000423 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000424 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000425 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000426 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000427 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000428 return
429 remhost = os.environ["REMOTE_HOST"]
430 remaddr = os.environ["REMOTE_ADDR"]
431 try:
432 os.unlink(name + "~")
433 except os.error:
434 pass
435 try:
436 os.rename(name, name + "~")
437 except os.error:
438 pass
439 try:
440 os.unlink(name)
441 except os.error:
442 pass
443 try:
444 f = open(name, "w")
445 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000446 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000447 return
448 now = time.ctime(time.time())
449 f.write("Title: %s\n" % title)
450 f.write("Last-Changed-Date: %s\n" % now)
451 f.write("Last-Changed-Author: %s\n" % author)
452 f.write("Last-Changed-Email: %s\n" % email)
453 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
454 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
455 keys = headers.keys()
456 keys.sort()
457 keys.remove('title')
458 for key in keys:
459 if key[:13] != 'last-changed-':
460 f.write("%s: %s\n" % (string.capwords(key, '-'),
461 headers[key]))
462 f.write("\n")
463 f.write(text)
464 f.write("\n")
465 f.close()
466
467 tfn = tempfile.mktemp()
468 f = open(tfn, "w")
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 f.write("\n")
475 f.write(log)
476 f.write("\n")
477 f.close()
478
479 p = os.popen("""
480 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
481 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
482 rm -f %s
483 """ % (name, name, tfn, tfn))
484 output = p.read()
485 sts = p.close()
486 if not sts:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000487 self.set_cookie(author, email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000488 self.prologue("Python FAQ Entry Edited")
489 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000490 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000491 if output:
492 print "<PRE>%s</PRE>" % cgi.escape(output)
493 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000494 self.error("Python FAQ Entry Commit Failed",
495 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000496 if output:
497 print "<PRE>%s</PRE>" % cgi.escape(output)
Guido van Rossum64099e91997-05-22 15:49:23 +0000498 print '<HR>'
499 print '<A HREF="faq.py?req=show&name=%s">Reload this entry.</A>' % name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000500
Guido van Rossumaf5be951997-05-22 16:57:50 +0000501 def set_cookie(self, author, email):
502 name = "Python-FAQ-ID"
503 value = "%s;%s" % (author, email)
504 import urllib
505 value = urllib.quote(value)
506 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
507 print "domain=%s;" % os.environ['HTTP_HOST'],
508 print "expires=Sat, 01-Jan-2000 00:00:00 GMT"
509
510 def get_cookie(self):
511 if not os.environ.has_key('HTTP_COOKIE'):
512 return "", ""
513 raw = os.environ['HTTP_COOKIE']
514 words = string.split(raw, ';')
515 cookies = {}
516 for word in words:
517 i = string.find(word, '=')
518 if i >= 0:
519 key, value = word[:i], word[i+1:]
520 cookies[key] = value
521 if not cookies.has_key('Python-FAQ-ID'):
522 return "", ""
523 value = cookies['Python-FAQ-ID']
524 import urllib
525 value = urllib.unquote(value)
526 i = string.rfind(value, ';')
527 author, email = value[:i], value[i+1:]
528 return author, email
529
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000530 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000531 author = self.author
532 email = self.email
533 if not author or not email:
534 a, e = self.get_cookie()
535 author = author or a
536 email = email or e
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000537 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000538 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000539 <TEXTAREA COLS=80 ROWS=20 NAME=text>""" % title
540 print cgi.escape(string.strip(text))
541 print """</TEXTAREA>
542 <BR>
543 Please provide the following information for logging purposes:
544 <BR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000545 <CODE>Name : </CODE><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
546 <BR>
547 <CODE>Email: </CODE><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
548 <BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000549 Log message (reason for the change):<BR>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000550 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA>
551 """ % (author, email, self.log)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000552
553 def showheaders(self, headers):
554 print "<UL>"
555 keys = map(string.lower, headers.keys())
556 keys.sort()
557 for key in keys:
558 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
559 headers[key] or '')
560 print "</UL>"
561
Guido van Rossumed531fd1997-05-22 15:21:57 +0000562 headers = None
563
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000564 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000565 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000566 import fnmatch, rfc822
567 if not fnmatch.fnmatch(name, NAMEPAT):
568 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000569 if self.add:
570 try:
571 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
572 string.atoi(self.number))
573 except ValueError:
574 return None, None
575 if fname != name:
576 return None, None
577 headers = {'title': "%s.%s. " % (self.section, self.number)}
578 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000579 else:
580 f = open(name)
581 headers = rfc822.Message(f)
582 text = f.read()
583 f.close()
584 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000585 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000586
Guido van Rossumed531fd1997-05-22 15:21:57 +0000587 def show(self, name, title, text, edit=1):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000588 # XXX Should put <A> tags around recognizable URLs
589 # XXX Should also turn "see section N" into hyperlinks
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000590 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000591 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000592 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000593 if not string.strip(line):
594 if pre:
595 print '</PRE>'
596 pre = 0
597 else:
598 print '<P>'
599 else:
Guido van Rossum5527db51997-05-23 04:44:30 +0000600 if line[0] not in string.whitespace:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000601 if pre:
602 print '</PRE>'
603 pre = 0
604 else:
605 if not pre:
606 print '<PRE>'
607 pre = 1
Guido van Rossum5527db51997-05-23 04:44:30 +0000608 print self.translate(line)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000609 if pre:
610 print '</PRE>'
611 pre = 0
612 print '<P>'
613 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000614 print """
615 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
616 <A HREF="faq.py?req=info&name=%s" TARGET=_blank>Log info</A>
617 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000618 if self.headers:
619 try:
620 date = self.headers['last-changed-date']
621 author = self.headers['last-changed-author']
622 email = self.headers['last-changed-email']
623 except KeyError:
624 pass
625 else:
Guido van Rossum64099e91997-05-22 15:49:23 +0000626 s = '/ Last changed on %s by <A HREF="%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000627 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000628 print '<P>'
629 print "<HR>"
630
Guido van Rossum74427e51997-05-21 23:43:39 +0000631 def getversion(self, name):
632 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000633 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000634 while 1:
635 line = p.readline()
636 if not line:
637 break
638 if line[:5] == 'head:':
639 head = string.strip(line[5:])
640 p.close()
641 return head
642
643 def prologue(self, title):
644 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000645 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000646 <HTML>
647 <HEAD>
648 <TITLE>%s</TITLE>
649 </HEAD>
650 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
651 BGCOLOR="#FFFFFF"
652 TEXT="#000000"
653 LINK="#AA0000"
654 VLINK="#906A6A">
655 <H1>%s</H1>
656 ''' % (title, title)
657
Guido van Rossumaf5be951997-05-22 16:57:50 +0000658 def error(self, *messages):
659 self.prologue("Python FAQ error")
660 print "Sorry, an error occurred:<BR>"
661 for message in messages:
662 print message,
663 print
664
Guido van Rossum74427e51997-05-21 23:43:39 +0000665 def epilogue(self):
666 print '''
667 <P>
668 <HR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000669 <A HREF="http://www.python.org">Python home</A> /
670 <A HREF="faq.py">FAQ home</A> /
Guido van Rossum64099e91997-05-22 15:49:23 +0000671 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
Guido van Rossum74427e51997-05-21 23:43:39 +0000672 </BODY>
673 </HTML>
674 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000675
Guido van Rossum5527db51997-05-23 04:44:30 +0000676 translate_prog = None
677
678 def translate(self, text):
679 if not self.translate_prog:
680 import regex
681 url = '\(http\|ftp\)://[^ \t\r\n]*'
682 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
683 self.translate_prog = prog = regex.compile(url + "\|" + email)
684 else:
685 prog = self.translate_prog
686 i = 0
687 list = []
688 while 1:
689 j = prog.search(text, i)
690 if j < 0:
691 break
692 list.append(cgi.escape(text[i:j]))
693 i = j
694 url = prog.group(0)
695 while url[-1] in ");:,.?":
696 url = url[:-1]
697 url = cgi.escape(url)
698 if ':' in url:
699 repl = '<A HREF="%s">%s</A>' % (url, url)
700 else:
701 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
702 list.append(repl)
703 i = i + len(url)
704 j = len(text)
705 list.append(cgi.escape(text[i:j]))
706 return string.join(list, '')
707
Guido van Rossumaf5be951997-05-22 16:57:50 +0000708print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000709dt = 0
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000710try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000711 import time
712 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000713 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000714 x = FAQServer()
715 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000716 t2 = time.time()
717 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000718except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000719 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000720 cgi.print_exception()
Guido van Rossum74427e51997-05-21 23:43:39 +0000721print "<P>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000722
723# The following bootstrap script must be placed in cgi-bin/faq.py:
724BOOTSTRAP = """
725#! /usr/local/bin/python
726FAQDIR = "/usr/people/guido/python/FAQ"
727import os, sys
728os.chdir(FAQDIR)
729sys.path.insert(0, os.curdir)
730import faqmain
731"""