blob: 5c7d8b2713f7168b442f93f4f22e705b36378831 [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 Rossuma0e9a6d1997-05-23 18:13:58 +000093 <FORM ACTION="faq.py">
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000094 <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)
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000615 elif '<' in line or '&' in line:
616 line = cgi.escape(line)
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000617 if not pre and '*' in line:
618 line = self.emphasize(line)
619 print line
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000620 if pre:
621 print '</PRE>'
622 pre = 0
623 print '<P>'
624 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000625 print """
626 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
627 <A HREF="faq.py?req=info&name=%s" TARGET=_blank>Log info</A>
628 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000629 if self.headers:
630 try:
631 date = self.headers['last-changed-date']
632 author = self.headers['last-changed-author']
633 email = self.headers['last-changed-email']
634 except KeyError:
635 pass
636 else:
Guido van Rossum64099e91997-05-22 15:49:23 +0000637 s = '/ Last changed on %s by <A HREF="%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000638 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000639 print '<P>'
640 print "<HR>"
641
Guido van Rossum74427e51997-05-21 23:43:39 +0000642 def getversion(self, name):
643 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000644 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000645 while 1:
646 line = p.readline()
647 if not line:
648 break
649 if line[:5] == 'head:':
650 head = string.strip(line[5:])
651 p.close()
652 return head
653
654 def prologue(self, title):
655 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000656 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000657 <HTML>
658 <HEAD>
659 <TITLE>%s</TITLE>
660 </HEAD>
661 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
662 BGCOLOR="#FFFFFF"
663 TEXT="#000000"
664 LINK="#AA0000"
665 VLINK="#906A6A">
666 <H1>%s</H1>
667 ''' % (title, title)
668
Guido van Rossumaf5be951997-05-22 16:57:50 +0000669 def error(self, *messages):
670 self.prologue("Python FAQ error")
671 print "Sorry, an error occurred:<BR>"
672 for message in messages:
673 print message,
674 print
675
Guido van Rossum74427e51997-05-21 23:43:39 +0000676 def epilogue(self):
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000677 if self.edit == 'no':
678 global wanttime
679 wanttime = 0
680 else:
681 print '''
682 <P>
683 <HR>
684 <A HREF="http://www.python.org">Python home</A> /
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000685 <A HREF="faq.py?req=frontpage">FAQ home</A> /
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000686 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
687 '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000688 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000689 </BODY>
690 </HTML>
691 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000692
Guido van Rossum5527db51997-05-23 04:44:30 +0000693 translate_prog = None
694
695 def translate(self, text):
696 if not self.translate_prog:
697 import regex
698 url = '\(http\|ftp\)://[^ \t\r\n]*'
699 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
700 self.translate_prog = prog = regex.compile(url + "\|" + email)
701 else:
702 prog = self.translate_prog
703 i = 0
704 list = []
705 while 1:
706 j = prog.search(text, i)
707 if j < 0:
708 break
709 list.append(cgi.escape(text[i:j]))
710 i = j
711 url = prog.group(0)
712 while url[-1] in ");:,.?":
713 url = url[:-1]
714 url = cgi.escape(url)
715 if ':' in url:
716 repl = '<A HREF="%s">%s</A>' % (url, url)
717 else:
718 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
719 list.append(repl)
720 i = i + len(url)
721 j = len(text)
722 list.append(cgi.escape(text[i:j]))
723 return string.join(list, '')
724
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000725 emphasize_prog = None
726
727 def emphasize(self, line):
728 import regsub
729 if not self.emphasize_prog:
730 import regex
731 pat = "\*\([a-zA-Z]+\)\*"
732 self.emphasize_prog = prog = regex.compile(pat)
733 else:
734 prog = self.emphasize_prog
735 return regsub.gsub(prog, "<I>\\1</I>", line)
736
Guido van Rossumaf5be951997-05-22 16:57:50 +0000737print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000738dt = 0
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000739wanttime = 1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000740try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000741 import time
742 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000743 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000744 x = FAQServer()
745 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000746 t2 = time.time()
747 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000748except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000749 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000750 cgi.print_exception()
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000751if wanttime:
752 print "<BR>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000753
754# The following bootstrap script must be placed in cgi-bin/faq.py:
755BOOTSTRAP = """
756#! /usr/local/bin/python
757FAQDIR = "/usr/people/guido/python/FAQ"
758import os, sys
759os.chdir(FAQDIR)
760sys.path.insert(0, os.curdir)
761import faqmain
762"""