blob: 6e00acf48a23d2dee417e8f9592b50bda20b21c1 [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
12- recognize urls and email addresses and turn them into <A> tags
Guido van Rossumd7bfa801997-05-21 21:31:39 +000013- use cookies to keep Name/email the same
14- explanation of editing somewhere
15- various embellishments, GIFs, crosslinks, hints, etc.
16- create new sections
17- rearrange entries
18- delete entries
Guido van Rossumd7bfa801997-05-21 21:31:39 +000019- send email on changes
20- optional staging of entries until reviewed?
Guido van Rossumd7bfa801997-05-21 21:31:39 +000021- freeze entries
22- username/password for editors
23- Change references to other Q's and whole sections
Guido van Rossumd7bfa801997-05-21 21:31:39 +000024- support adding annotations, too
Guido van Rossumed531fd1997-05-22 15:21:57 +000025- make it more generic (so you can create your own FAQ)
Guido van Rossumc6447521997-05-23 00:50:01 +000026- more OO structure, e.g. add a class representing one FAQ entry
Guido van Rossumd7bfa801997-05-21 21:31:39 +000027
28"""
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000029
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000030NAMEPAT = "faq??.???.htp"
Guido van Rossumd7bfa801997-05-21 21:31:39 +000031NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000032
33class FAQServer:
34
35 def __init__(self):
36 pass
37
38 def main(self):
39 self.form = cgi.FieldStorage()
40 req = self.req or 'frontpage'
41 try:
42 method = getattr(self, 'do_%s' % req)
43 except AttributeError:
44 print "Unrecognized request type", req
45 else:
46 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000047 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000048
Guido van Rossum3c3354c1997-05-21 16:52:18 +000049 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000050 'author', 'email', 'log', 'section', 'number', 'add',
51 'version']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000052
53 def __getattr__(self, key):
54 if key not in self.KEYS:
55 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000056 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000057 form = self.form
58 try:
59 item = form[key]
60 except TypeError, msg:
61 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000062 except KeyError:
63 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000064 value = self.form[key].value
65 value = string.strip(value)
66 setattr(self, key, value)
67 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000068
69 def do_frontpage(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000070 self.prologue("Python FAQ (alpha) Front Page")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000071 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000072 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000073 <LI><A HREF="faq.py?req=index">FAQ index</A>
74 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +000075 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +000076 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
77 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +000078 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000079 </UL>
80
Guido van Rossumd7bfa801997-05-21 21:31:39 +000081 <H2>Search the FAQ</H2>
82
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000083 <FORM ACTION="faq.py?req=query">
84 <INPUT TYPE=text NAME=query>
85 <INPUT TYPE=submit VALUE="Search">
86 <INPUT TYPE=hidden NAME=req VALUE=query>
87 </FORM>
88
89 Disclaimer: these pages are intended to be edited by anyone.
90 Please exercise discretion when editing, don't be rude, etc.
91 """
92
Guido van Rossumd7bfa801997-05-21 21:31:39 +000093 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +000094 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +000095 names = os.listdir(os.curdir)
96 names.sort()
97 section = None
98 for name in names:
99 headers, text = self.read(name)
100 if headers:
101 title = headers['title']
102 i = string.find(title, '.')
103 nsec = title[:i]
104 if nsec != section:
105 if section:
106 print """
107 <P>
108 <LI><A HREF="faq.py?req=add&amp;section=%s"
109 >Add new entry</A> (at this point)
110 </UL>
111 """ % section
112 section = nsec
113 print "<H2>Section %s</H2>" % section
114 print "<UL>"
115 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
116 name, cgi.escape(title))
117 if section:
118 print """
119 <P>
120 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
121 (at this point)
122 </UL>
123 """ % section
124 else:
125 print "No FAQ entries?!?!"
126
127 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000128 self.prologue("Python FAQ Entry")
129 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000130 name = self.name
131 headers, text = self.read(name)
132 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000133 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000134 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000135 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000136
137 def do_all(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000138 self.prologue("The Whole Python FAQ")
139 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000140 names = os.listdir(os.curdir)
141 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000142 section = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000143 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000144 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000145 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000146 title = headers['title']
147 i = string.find(title, '.')
148 nsec = title[:i]
149 if nsec != section:
150 section = nsec
151 print "<H1>Section %s</H1>" % section
152 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000153 self.show(name, title, text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000154 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000155 print "No FAQ entries?!?!"
156
157 def do_roulette(self):
158 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000159 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000160 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000161 Please check the correctness of the entry below.
162 If you find any problems, please edit the entry.
163 <P>
164 <HR>
165 """
166 names = os.listdir(os.curdir)
167 while names:
168 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000169 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000170 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000171 self.show(name, headers['title'], text)
172 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000173 break
174 else:
175 names.remove(name)
176 else:
177 print "No FAQ entries?!?!"
178
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000179 def do_recent(self):
180 import fnmatch, stat
181 names = os.listdir(os.curdir)
182 now = time.time()
183 list = []
184 for name in names:
185 if not fnmatch.fnmatch(name, NAMEPAT):
186 continue
187 try:
188 st = os.stat(name)
189 except os.error:
190 continue
191 tuple = (st[stat.ST_MTIME], name)
192 list.append(tuple)
193 list.sort()
194 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000195 self.prologue("Python FAQ, Most Recently Modified First")
196 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000197 n = 0
198 for (mtime, name) in list:
199 headers, text = self.read(name)
Guido van Rossumc6447521997-05-23 00:50:01 +0000200 if headers and headers.has_key('last-changed-date'):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000201 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000202 n = n+1
203 if not n:
204 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000205
206 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000207 query = self.query
208 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000209 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000210 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000211 import regex
212 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000213 p = regex.compile(query, regex.casefold)
214 names = os.listdir(os.curdir)
215 names.sort()
216 print "<HR>"
217 n = 0
218 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000219 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000220 if headers:
221 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000222 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000223 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000224 n = n+1
225 if not n:
226 print "No hits."
227
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000228 def do_add(self):
229 section = self.section
230 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000231 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000232 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000233 Go to the <A HREF="faq.py?req=index">FAQ index</A>
234 and click on the "Add new entry" link at the end
235 of the section to which you want to add the entry.
236 """
237 return
238 try:
239 nsec = string.atoi(section)
240 except ValueError:
241 print "Bad section number", nsec
242 names = os.listdir(os.curdir)
243 max = 0
244 import regex
245 prog = regex.compile(NAMEREG)
246 for name in names:
247 if prog.match(name) >= 0:
248 s1, s2 = prog.group(1, 2)
249 n1, n2 = string.atoi(s1), string.atoi(s2)
250 if n1 == nsec:
251 if n2 > max:
252 max = n2
253 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000254 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000255 return
256 num = max+1
257 name = "faq%02d.%03d.htp" % (nsec, num)
258 self.name = name
259 self.add = "yes"
260 self.number = str(num)
261 self.do_edit()
262
Guido van Rossumaf5be951997-05-22 16:57:50 +0000263 def do_delete(self):
264 self.prologue("How to delete a FAQ entry")
265 print """
266 At the moment, there's no direct way to delete entries.
267 This is because the entry numbers are also their
268 unique identifiers -- it's a bad idea to renumber entries.
269 <P>
270 If you really think an entry needs to be deleted,
271 change the title to "(deleted)" and make the body
272 empty (keep the entry number in the title though).
273 """
274
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000275 def do_edit(self):
276 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000277 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000278 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000279 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000280 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000281 self.prologue("Python FAQ Edit Form")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000282 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000283 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000284 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000285 self.showedit(name, title, text)
286 if self.add:
287 print """
288 <INPUT TYPE=hidden NAME=add VALUE=%s>
289 <INPUT TYPE=hidden NAME=section VALUE=%s>
290 <INPUT TYPE=hidden NAME=number VALUE=%s>
291 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000292 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000293 <INPUT TYPE=submit VALUE="Review Edit">
294 <INPUT TYPE=hidden NAME=req VALUE=review>
295 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000296 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000297 </FORM>
298 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000299 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000300 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000301
302 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000303 if self.commit:
304 self.checkin()
305 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000306 name = self.name
307 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000308 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000309 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000310 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000311 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000312 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000313 if self.author and '@' in self.email:
314 self.set_cookie(self.author, self.email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000315 self.prologue("Python FAQ Review Form")
316 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000317 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000318 print "<FORM METHOD=POST ACTION=faq.py>"
319 if self.log and self.author and '@' in self.email:
320 print """
321 <INPUT TYPE=submit NAME=commit VALUE="Commit">
322 Click this button to commit the change.
323 <P>
324 <HR>
325 <P>
326 """
327 else:
328 print """
329 To commit this change, please enter your name,
330 email and a log message in the form below.
331 <P>
332 <HR>
333 <P>
334 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000335 self.showedit(name, title, text)
336 if self.add:
337 print """
338 <INPUT TYPE=hidden NAME=add VALUE=%s>
339 <INPUT TYPE=hidden NAME=section VALUE=%s>
340 <INPUT TYPE=hidden NAME=number VALUE=%s>
341 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000342 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000343 <BR>
344 <INPUT TYPE=submit VALUE="Review Edit">
345 <INPUT TYPE=hidden NAME=req VALUE=review>
346 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000347 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000348 </FORM>
349 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000350 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000351
Guido van Rossumf701bf11997-05-21 22:25:56 +0000352 def do_info(self):
353 name = self.name
354 headers, text = self.read(name)
355 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000356 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000357 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000358 self.prologue("Info for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000359 print '<PRE>'
360 sys.stdout.flush()
361 os.system("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" % self.name)
362 print '</PRE>'
363 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
364
365 def do_rlog(self):
366 name = self.name
367 headers, text = self.read(name)
368 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000369 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000370 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000371 self.prologue("RCS log for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000372 print '<PRE>'
373 sys.stdout.flush()
374 os.system("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
375 print '</PRE>'
376
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000377 def checkin(self):
378 import regsub, time, tempfile
379 name = self.name
380
381 headers, oldtext = self.read(name)
382 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000383 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000384 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000385 version = self.version
386 curversion = self.getversion(name)
387 if version != curversion:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000388 self.error("Version conflict.",
389 "You edited version %s but current version is %s." % (
390 version, curversion),
391 '<A HREF="faq.py?req=show&name=%s">Reload.</A>' % name)
Guido van Rossum74427e51997-05-21 23:43:39 +0000392 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000393 text = self.text
394 title = self.title
395 author = self.author
396 email = self.email
397 log = self.log
398 text = regsub.gsub("\r\n", "\n", text)
399 log = regsub.gsub("\r\n", "\n", log)
400 author = string.join(string.split(author))
401 email = string.join(string.split(email))
402 title = string.join(string.split(title))
403 oldtitle = headers['title']
404 oldtitle = string.join(string.split(oldtitle))
405 text = string.strip(text)
406 oldtext = string.strip(oldtext)
407 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000408 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000409 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000410 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000411 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000412 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000413 return
414 remhost = os.environ["REMOTE_HOST"]
415 remaddr = os.environ["REMOTE_ADDR"]
416 try:
417 os.unlink(name + "~")
418 except os.error:
419 pass
420 try:
421 os.rename(name, name + "~")
422 except os.error:
423 pass
424 try:
425 os.unlink(name)
426 except os.error:
427 pass
428 try:
429 f = open(name, "w")
430 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000431 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000432 return
433 now = time.ctime(time.time())
434 f.write("Title: %s\n" % title)
435 f.write("Last-Changed-Date: %s\n" % now)
436 f.write("Last-Changed-Author: %s\n" % author)
437 f.write("Last-Changed-Email: %s\n" % email)
438 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
439 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
440 keys = headers.keys()
441 keys.sort()
442 keys.remove('title')
443 for key in keys:
444 if key[:13] != 'last-changed-':
445 f.write("%s: %s\n" % (string.capwords(key, '-'),
446 headers[key]))
447 f.write("\n")
448 f.write(text)
449 f.write("\n")
450 f.close()
451
452 tfn = tempfile.mktemp()
453 f = open(tfn, "w")
454 f.write("Last-Changed-Date: %s\n" % now)
455 f.write("Last-Changed-Author: %s\n" % author)
456 f.write("Last-Changed-Email: %s\n" % email)
457 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
458 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
459 f.write("\n")
460 f.write(log)
461 f.write("\n")
462 f.close()
463
464 p = os.popen("""
465 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
466 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
467 rm -f %s
468 """ % (name, name, tfn, tfn))
469 output = p.read()
470 sts = p.close()
471 if not sts:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000472 self.set_cookie(author, email)
Guido van Rossum74427e51997-05-21 23:43:39 +0000473 self.prologue("Python FAQ Entry Edited")
474 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000475 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000476 if output:
477 print "<PRE>%s</PRE>" % cgi.escape(output)
478 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000479 self.error("Python FAQ Entry Commit Failed",
480 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000481 if output:
482 print "<PRE>%s</PRE>" % cgi.escape(output)
Guido van Rossum64099e91997-05-22 15:49:23 +0000483 print '<HR>'
484 print '<A HREF="faq.py?req=show&name=%s">Reload this entry.</A>' % name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000485
Guido van Rossumaf5be951997-05-22 16:57:50 +0000486 def set_cookie(self, author, email):
487 name = "Python-FAQ-ID"
488 value = "%s;%s" % (author, email)
489 import urllib
490 value = urllib.quote(value)
491 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
492 print "domain=%s;" % os.environ['HTTP_HOST'],
493 print "expires=Sat, 01-Jan-2000 00:00:00 GMT"
494
495 def get_cookie(self):
496 if not os.environ.has_key('HTTP_COOKIE'):
497 return "", ""
498 raw = os.environ['HTTP_COOKIE']
499 words = string.split(raw, ';')
500 cookies = {}
501 for word in words:
502 i = string.find(word, '=')
503 if i >= 0:
504 key, value = word[:i], word[i+1:]
505 cookies[key] = value
506 if not cookies.has_key('Python-FAQ-ID'):
507 return "", ""
508 value = cookies['Python-FAQ-ID']
509 import urllib
510 value = urllib.unquote(value)
511 i = string.rfind(value, ';')
512 author, email = value[:i], value[i+1:]
513 return author, email
514
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000515 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000516 author = self.author
517 email = self.email
518 if not author or not email:
519 a, e = self.get_cookie()
520 author = author or a
521 email = email or e
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000522 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000523 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000524 <TEXTAREA COLS=80 ROWS=20 NAME=text>""" % title
525 print cgi.escape(string.strip(text))
526 print """</TEXTAREA>
527 <BR>
528 Please provide the following information for logging purposes:
529 <BR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000530 <CODE>Name : </CODE><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
531 <BR>
532 <CODE>Email: </CODE><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
533 <BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000534 Log message (reason for the change):<BR>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000535 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA>
536 """ % (author, email, self.log)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000537
538 def showheaders(self, headers):
539 print "<UL>"
540 keys = map(string.lower, headers.keys())
541 keys.sort()
542 for key in keys:
543 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
544 headers[key] or '')
545 print "</UL>"
546
Guido van Rossumed531fd1997-05-22 15:21:57 +0000547 headers = None
548
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000549 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000550 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000551 import fnmatch, rfc822
552 if not fnmatch.fnmatch(name, NAMEPAT):
553 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000554 if self.add:
555 try:
556 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
557 string.atoi(self.number))
558 except ValueError:
559 return None, None
560 if fname != name:
561 return None, None
562 headers = {'title': "%s.%s. " % (self.section, self.number)}
563 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000564 else:
565 f = open(name)
566 headers = rfc822.Message(f)
567 text = f.read()
568 f.close()
569 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000570 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000571
Guido van Rossumed531fd1997-05-22 15:21:57 +0000572 def show(self, name, title, text, edit=1):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000573 # XXX Should put <A> tags around recognizable URLs
574 # XXX Should also turn "see section N" into hyperlinks
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000575 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000576 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000577 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000578 if not string.strip(line):
579 if pre:
580 print '</PRE>'
581 pre = 0
582 else:
583 print '<P>'
584 else:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000585 if line == string.lstrip(line): # I.e., no leading whitespace
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000586 if pre:
587 print '</PRE>'
588 pre = 0
589 else:
590 if not pre:
591 print '<PRE>'
592 pre = 1
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000593 print cgi.escape(line)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000594 if pre:
595 print '</PRE>'
596 pre = 0
597 print '<P>'
598 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000599 print """
600 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
601 <A HREF="faq.py?req=info&name=%s" TARGET=_blank>Log info</A>
602 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000603 if self.headers:
604 try:
605 date = self.headers['last-changed-date']
606 author = self.headers['last-changed-author']
607 email = self.headers['last-changed-email']
608 except KeyError:
609 pass
610 else:
Guido van Rossum64099e91997-05-22 15:49:23 +0000611 s = '/ Last changed on %s by <A HREF="%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000612 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000613 print '<P>'
614 print "<HR>"
615
Guido van Rossum74427e51997-05-21 23:43:39 +0000616 def getversion(self, name):
617 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000618 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000619 while 1:
620 line = p.readline()
621 if not line:
622 break
623 if line[:5] == 'head:':
624 head = string.strip(line[5:])
625 p.close()
626 return head
627
628 def prologue(self, title):
629 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000630 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000631 <HTML>
632 <HEAD>
633 <TITLE>%s</TITLE>
634 </HEAD>
635 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
636 BGCOLOR="#FFFFFF"
637 TEXT="#000000"
638 LINK="#AA0000"
639 VLINK="#906A6A">
640 <H1>%s</H1>
641 ''' % (title, title)
642
Guido van Rossumaf5be951997-05-22 16:57:50 +0000643 def error(self, *messages):
644 self.prologue("Python FAQ error")
645 print "Sorry, an error occurred:<BR>"
646 for message in messages:
647 print message,
648 print
649
Guido van Rossum74427e51997-05-21 23:43:39 +0000650 def epilogue(self):
651 print '''
652 <P>
653 <HR>
Guido van Rossumed531fd1997-05-22 15:21:57 +0000654 <A HREF="http://www.python.org">Python home</A> /
655 <A HREF="faq.py">FAQ home</A> /
Guido van Rossum64099e91997-05-22 15:49:23 +0000656 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
Guido van Rossum74427e51997-05-21 23:43:39 +0000657 </BODY>
658 </HTML>
659 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000660
Guido van Rossumaf5be951997-05-22 16:57:50 +0000661print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000662dt = 0
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000663try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000664 import time
665 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000666 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000667 x = FAQServer()
668 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000669 t2 = time.time()
670 dt = t2-t1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000671except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000672 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000673 cgi.print_exception()
Guido van Rossum74427e51997-05-21 23:43:39 +0000674print "<P>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000675
676# The following bootstrap script must be placed in cgi-bin/faq.py:
677BOOTSTRAP = """
678#! /usr/local/bin/python
679FAQDIR = "/usr/people/guido/python/FAQ"
680import os, sys
681os.chdir(FAQDIR)
682sys.path.insert(0, os.curdir)
683import faqmain
684"""