blob: 7550bd002e33c5d52b8e074a530b4e3f1f920312 [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 Rossum1dcc2441997-05-23 18:53:06 +00009XXX User Features TO DO
10
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000011- next/prev/index links in do_show???
Guido van Rossumd7bfa801997-05-21 21:31:39 +000012- explanation of editing somewhere
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000013- embellishments, GIFs, hints, etc.
Guido van Rossum1dcc2441997-05-23 18:53:06 +000014- support adding annotations, too
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000015- restrict recent changes to last week (or make it an option)
16- extended search capabilities
Guido van Rossum1dcc2441997-05-23 18:53:06 +000017
18XXX Management Features TO DO
19
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000020- username/password for authors
Guido van Rossumd7bfa801997-05-21 21:31:39 +000021- create new sections
22- rearrange entries
23- delete entries
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000024- freeze entries
Guido van Rossum1dcc2441997-05-23 18:53:06 +000025- send email on changes?
26- send email on ERRORS!
Guido van Rossumd7bfa801997-05-21 21:31:39 +000027- optional staging of entries until reviewed?
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000028 (could be done using rcs branches!)
29- prevent race conditions on nearly simultaneous commits
30
31XXX Performance
32
33- could cache generated HTML
34- could speed up searches with a separate index file
Guido van Rossum1dcc2441997-05-23 18:53:06 +000035
36XXX Code organization TO DO
37
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000038- read section titles from a file (could be a Python file: import faqcustom)
Guido van Rossum1dcc2441997-05-23 18:53:06 +000039- customize rcs command pathnames (and everything else)
Guido van Rossumed531fd1997-05-22 15:21:57 +000040- make it more generic (so you can create your own FAQ)
Guido van Rossumc6447521997-05-23 00:50:01 +000041- more OO structure, e.g. add a class representing one FAQ entry
Guido van Rossumd7bfa801997-05-21 21:31:39 +000042
43"""
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000044
Guido van Rossumf8f0fb71997-05-23 21:21:43 +000045# NB for timing purposes, the imports are at the end of this file
46
Guido van Rossuma78a3c31997-05-23 22:29:24 +000047PASSWORD = "Spam"
48
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000049NAMEPAT = "faq??.???.htp"
Guido van Rossumd7bfa801997-05-21 21:31:39 +000050NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000051
Guido van Rossum4888c7e1997-05-23 15:55:19 +000052SECTIONS = {
53 "1": "General information and availability",
54 "2": "Python in the real world",
55 "3": "Building Python and Other Known Bugs",
56 "4": "Programming in Python",
57 "5": "Extending Python",
58 "6": "Python's design",
59 "7": "Using Python on non-UNIX platforms",
60}
61
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000062class FAQServer:
63
64 def __init__(self):
65 pass
66
67 def main(self):
68 self.form = cgi.FieldStorage()
69 req = self.req or 'frontpage'
70 try:
71 method = getattr(self, 'do_%s' % req)
72 except AttributeError:
73 print "Unrecognized request type", req
74 else:
75 method()
Guido van Rossum74427e51997-05-21 23:43:39 +000076 self.epilogue()
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000077
Guido van Rossum3c3354c1997-05-21 16:52:18 +000078 KEYS = ['req', 'query', 'name', 'text', 'commit', 'title',
Guido van Rossum74427e51997-05-21 23:43:39 +000079 'author', 'email', 'log', 'section', 'number', 'add',
Guido van Rossuma78a3c31997-05-23 22:29:24 +000080 'version', 'edit', 'password']
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000081
82 def __getattr__(self, key):
83 if key not in self.KEYS:
84 raise AttributeError
Guido van Rossum3c3354c1997-05-21 16:52:18 +000085 try:
Guido van Rossumed531fd1997-05-22 15:21:57 +000086 form = self.form
87 try:
88 item = form[key]
89 except TypeError, msg:
90 raise KeyError, msg, sys.exc_traceback
Guido van Rossum3c3354c1997-05-21 16:52:18 +000091 except KeyError:
92 return ''
Guido van Rossumaf5be951997-05-22 16:57:50 +000093 value = self.form[key].value
94 value = string.strip(value)
95 setattr(self, key, value)
96 return value
Guido van Rossumadb3a9d1997-05-21 07:24:50 +000097
98 def do_frontpage(self):
Guido van Rossum9c6ceda1997-05-23 22:44:01 +000099 self.prologue("Python FAQ Wizard (beta test)")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000100 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000101 <UL>
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000102 <LI><A HREF="faq.py?req=index">FAQ index</A>
103 <LI><A HREF="faq.py?req=all">The whole FAQ</A>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000104 <LI><A HREF="faq.py?req=roulette">FAQ roulette</A>
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000105 <LI><A HREF="faq.py?req=recent">Recently changed FAQ entries</A>
106 <LI><A HREF="faq.py?req=add">Add a new FAQ entry</A>
Guido van Rossumaf5be951997-05-22 16:57:50 +0000107 <LI><A HREF="faq.py?req=delete">Delete a FAQ entry</A>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000108 </UL>
109
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000110 <HR>
111
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000112 <H2>Search the FAQ</H2>
113
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000114 <FORM ACTION="faq.py">
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000115 <INPUT TYPE=text NAME=query>
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000116 <INPUT TYPE=submit VALUE="Search"><BR>
117 (Case insensitive regular expressions.)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000118 <INPUT TYPE=hidden NAME=req VALUE=query>
119 </FORM>
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000120 <HR>
121 <P>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000122 Disclaimer: these pages are intended to be edited by anyone.
123 Please exercise discretion when editing, don't be rude, etc.
124 """
125
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000126 def do_index(self):
Guido van Rossum74427e51997-05-21 23:43:39 +0000127 self.prologue("Python FAQ Index")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000128 names = os.listdir(os.curdir)
129 names.sort()
130 section = None
131 for name in names:
132 headers, text = self.read(name)
133 if headers:
134 title = headers['title']
135 i = string.find(title, '.')
136 nsec = title[:i]
137 if nsec != section:
138 if section:
139 print """
140 <P>
141 <LI><A HREF="faq.py?req=add&amp;section=%s"
142 >Add new entry</A> (at this point)
143 </UL>
144 """ % section
145 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000146 if SECTIONS.has_key(section):
147 stitle = SECTIONS[section]
148 else:
149 stitle = ""
150 print "<H2>Section %s. %s</H2>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000151 print "<UL>"
152 print '<LI><A HREF="faq.py?req=show&name=%s">%s</A>' % (
153 name, cgi.escape(title))
154 if section:
155 print """
156 <P>
157 <LI><A HREF="faq.py?req=add&amp;section=%s">Add new entry</A>
158 (at this point)
159 </UL>
160 """ % section
161 else:
162 print "No FAQ entries?!?!"
163
164 def do_show(self):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000165 self.prologue("Python FAQ Entry")
166 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000167 name = self.name
168 headers, text = self.read(name)
169 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000170 self.error("Invalid file name", name)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000171 return
Guido van Rossumed531fd1997-05-22 15:21:57 +0000172 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000173
174 def do_all(self):
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000175 import fnmatch, stat
Guido van Rossum74427e51997-05-21 23:43:39 +0000176 self.prologue("The Whole Python FAQ")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000177 names = os.listdir(os.curdir)
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000178 lastmtime = 0
179 for name in names:
180 if not fnmatch.fnmatch(name, NAMEPAT):
181 continue
182 try:
183 st = os.stat(name)
184 except os.error:
185 continue
186 lastmtime = max(lastmtime, st[stat.ST_MTIME])
187 if lastmtime:
188 print time.strftime("Last changed on %c %Z",
189 time.localtime(lastmtime))
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000190 names.sort()
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000191 section = None
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000192 print "<HR>"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000193 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000194 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000195 if headers:
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000196 title = headers['title']
197 i = string.find(title, '.')
198 nsec = title[:i]
199 if nsec != section:
200 section = nsec
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000201 if SECTIONS.has_key(section):
202 stitle = SECTIONS[section]
203 else:
204 stitle = ""
205 print "<H1>Section %s. %s</H1>" % (section, stitle)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000206 print "<HR>"
Guido van Rossum4888c7e1997-05-23 15:55:19 +0000207 self.show(name, title, text, edit=(self.edit != 'no'))
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000208 if not section:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000209 print "No FAQ entries?!?!"
210
211 def do_roulette(self):
212 import whrandom
Guido van Rossum74427e51997-05-21 23:43:39 +0000213 self.prologue("Python FAQ Roulette")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000214 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000215 Please check the correctness of the entry below.
216 If you find any problems, please edit the entry.
217 <P>
218 <HR>
219 """
220 names = os.listdir(os.curdir)
221 while names:
222 name = whrandom.choice(names)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000223 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000224 if headers:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000225 self.show(name, headers['title'], text)
226 print "<P>Use `Reload' to show another one."
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000227 break
228 else:
229 names.remove(name)
230 else:
231 print "No FAQ entries?!?!"
232
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000233 def do_recent(self):
234 import fnmatch, stat
235 names = os.listdir(os.curdir)
236 now = time.time()
237 list = []
238 for name in names:
239 if not fnmatch.fnmatch(name, NAMEPAT):
240 continue
241 try:
242 st = os.stat(name)
243 except os.error:
244 continue
245 tuple = (st[stat.ST_MTIME], name)
246 list.append(tuple)
247 list.sort()
248 list.reverse()
Guido van Rossum74427e51997-05-21 23:43:39 +0000249 self.prologue("Python FAQ, Most Recently Modified First")
250 print "<HR>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000251 n = 0
252 for (mtime, name) in list:
253 headers, text = self.read(name)
Guido van Rossumc6447521997-05-23 00:50:01 +0000254 if headers and headers.has_key('last-changed-date'):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000255 self.show(name, headers['title'], text)
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000256 n = n+1
257 if not n:
258 print "No FAQ entries?!?!"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000259
260 def do_query(self):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000261 query = self.query
262 if not query:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000263 self.error("No query string")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000264 return
Guido van Rossumaf5be951997-05-22 16:57:50 +0000265 import regex
266 self.prologue("Python FAQ Query Results")
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000267 p = regex.compile(query, regex.casefold)
268 names = os.listdir(os.curdir)
269 names.sort()
270 print "<HR>"
271 n = 0
272 for name in names:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000273 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000274 if headers:
275 title = headers['title']
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000276 if p.search(title) >= 0 or p.search(text) >= 0:
Guido van Rossumed531fd1997-05-22 15:21:57 +0000277 self.show(name, title, text)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000278 n = n+1
279 if not n:
280 print "No hits."
281
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000282 def do_add(self):
283 section = self.section
284 if not section:
Guido van Rossum74427e51997-05-21 23:43:39 +0000285 self.prologue("How to add a new FAQ entry")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000286 print """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000287 Go to the <A HREF="faq.py?req=index">FAQ index</A>
288 and click on the "Add new entry" link at the end
289 of the section to which you want to add the entry.
290 """
291 return
292 try:
293 nsec = string.atoi(section)
294 except ValueError:
295 print "Bad section number", nsec
296 names = os.listdir(os.curdir)
297 max = 0
298 import regex
299 prog = regex.compile(NAMEREG)
300 for name in names:
301 if prog.match(name) >= 0:
302 s1, s2 = prog.group(1, 2)
303 n1, n2 = string.atoi(s1), string.atoi(s2)
304 if n1 == nsec:
305 if n2 > max:
306 max = n2
307 if not max:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000308 self.error("Can't add new sections yet.")
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000309 return
310 num = max+1
311 name = "faq%02d.%03d.htp" % (nsec, num)
312 self.name = name
313 self.add = "yes"
314 self.number = str(num)
315 self.do_edit()
316
Guido van Rossumaf5be951997-05-22 16:57:50 +0000317 def do_delete(self):
318 self.prologue("How to delete a FAQ entry")
319 print """
320 At the moment, there's no direct way to delete entries.
321 This is because the entry numbers are also their
322 unique identifiers -- it's a bad idea to renumber entries.
323 <P>
324 If you really think an entry needs to be deleted,
325 change the title to "(deleted)" and make the body
326 empty (keep the entry number in the title though).
327 """
328
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000329 def do_edit(self):
330 name = self.name
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000331 headers, text = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000332 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000333 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000334 return
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000335 self.prologue("Python FAQ Edit Wizard - Edit Form")
336 print '<A HREF="/python/faqhelp.html">Click for Help</A>'
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000337 title = headers['title']
Guido van Rossum74427e51997-05-21 23:43:39 +0000338 version = self.getversion(name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000339 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000340 self.showedit(name, title, text)
341 if self.add:
342 print """
343 <INPUT TYPE=hidden NAME=add VALUE=%s>
344 <INPUT TYPE=hidden NAME=section VALUE=%s>
345 <INPUT TYPE=hidden NAME=number VALUE=%s>
346 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000347 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000348 <INPUT TYPE=submit VALUE="Review Edit">
349 <INPUT TYPE=hidden NAME=req VALUE=review>
350 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000351 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000352 </FORM>
353 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000354 """ % (name, version)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000355 self.show(name, title, text, edit=0)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000356
357 def do_review(self):
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000358 if self.commit:
359 self.checkin()
360 return
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000361 name = self.name
362 text = self.text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000363 title = self.title
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000364 headers, oldtext = self.read(name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000365 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000366 self.error("Invalid file name", name)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000367 return
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000368 if self.author or '@' in self.email or self.password:
369 self.set_cookie(self.author, self.email, self.password)
370 self.prologue("Python FAQ Edit Wizard - Review Form")
371 print '<A HREF="/python/faqhelp.html">Click for Help</A>'
Guido van Rossum74427e51997-05-21 23:43:39 +0000372 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000373 self.show(name, title, text, edit=0)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000374 print "<FORM METHOD=POST ACTION=faq.py>"
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000375 if self.password == PASSWORD \
376 and self.log and self.author and '@' in self.email:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000377 print """
378 <INPUT TYPE=submit NAME=commit VALUE="Commit">
379 Click this button to commit the change.
380 <P>
381 <HR>
382 <P>
383 """
384 else:
385 print """
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000386 To commit this change, please enter a log message,
387 your name, your email address,
388 and the correct password in the form below.
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000389 <P>
390 <HR>
391 <P>
392 """
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000393 self.showedit(name, title, text)
394 if self.add:
395 print """
396 <INPUT TYPE=hidden NAME=add VALUE=%s>
397 <INPUT TYPE=hidden NAME=section VALUE=%s>
398 <INPUT TYPE=hidden NAME=number VALUE=%s>
399 """ % (self.add, self.section, self.number)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000400 print """
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000401 <BR>
402 <INPUT TYPE=submit VALUE="Review Edit">
403 <INPUT TYPE=hidden NAME=req VALUE=review>
404 <INPUT TYPE=hidden NAME=name VALUE=%s>
Guido van Rossum74427e51997-05-21 23:43:39 +0000405 <INPUT TYPE=hidden NAME=version VALUE=%s>
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000406 </FORM>
407 <HR>
Guido van Rossum74427e51997-05-21 23:43:39 +0000408 """ % (name, self.version)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000409
Guido van Rossumf701bf11997-05-21 22:25:56 +0000410 def do_info(self):
411 name = self.name
412 headers, text = self.read(name)
413 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000414 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000415 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000416 self.prologue("Info for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000417 print '<PRE>'
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000418 p = os.popen("/depot/gnu/plat/bin/rlog -r %s </dev/null 2>&1" %
419 self.name)
420 output = p.read()
421 p.close()
422 print cgi.escape(output)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000423 print '</PRE>'
424 print '<A HREF="faq.py?req=rlog&name=%s">View full rcs log</A>' % name
425
426 def do_rlog(self):
427 name = self.name
428 headers, text = self.read(name)
429 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000430 self.error("Invalid file name", name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000431 return
Guido van Rossumc6447521997-05-23 00:50:01 +0000432 self.prologue("RCS log for %s" % name)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000433 print '<PRE>'
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000434 p = os.popen("/depot/gnu/plat/bin/rlog %s </dev/null 2>&1" % self.name)
435 output = p.read()
436 p.close()
437 print cgi.escape(output)
Guido van Rossumf701bf11997-05-21 22:25:56 +0000438 print '</PRE>'
439
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000440 def checkin(self):
441 import regsub, time, tempfile
442 name = self.name
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000443 password = self.password
444 if password != PASSWORD:
445 self.error("Invalid password.")
446 return
447 if not (self.log and self.author and '@' in self.email):
448 self.error("No log message, no author, or invalid email.")
449 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000450 headers, oldtext = self.read(name)
451 if not headers:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000452 self.error("Invalid file name", name)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000453 return
Guido van Rossum74427e51997-05-21 23:43:39 +0000454 version = self.version
455 curversion = self.getversion(name)
456 if version != curversion:
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000457 self.error(
458 "Version conflict.",
459 "You edited version %s but current version is %s." % (
460 version, curversion),
461 """
462 <P>
463 The two most common causes of this problem are:
464 <UL>
465 <LI>After committing a change, you went back in your browser,
466 edited the entry some more, and clicked Commit again.
467 <LI>Someone else started editing the same entry and committed
468 before you did.
469 </UL>
470 <P>
471 """,
472 '<A HREF="faq.py?req=show&name=%s"' % name,
473 '>Click here to reload the entry and try again.</A>')
Guido van Rossum74427e51997-05-21 23:43:39 +0000474 return
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000475 text = self.text
476 title = self.title
477 author = self.author
478 email = self.email
479 log = self.log
480 text = regsub.gsub("\r\n", "\n", text)
481 log = regsub.gsub("\r\n", "\n", log)
482 author = string.join(string.split(author))
483 email = string.join(string.split(email))
484 title = string.join(string.split(title))
485 oldtitle = headers['title']
486 oldtitle = string.join(string.split(oldtitle))
487 text = string.strip(text)
488 oldtext = string.strip(oldtext)
489 if text == oldtext and title == oldtitle:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000490 self.error("No changes.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000491 return
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000492 # Check that the FAQ entry number didn't change
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000493 if string.split(title)[:1] != string.split(oldtitle)[:1]:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000494 self.error("Don't change the FAQ entry number please.")
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000495 return
496 remhost = os.environ["REMOTE_HOST"]
497 remaddr = os.environ["REMOTE_ADDR"]
498 try:
499 os.unlink(name + "~")
500 except os.error:
501 pass
502 try:
503 os.rename(name, name + "~")
504 except os.error:
505 pass
506 try:
507 os.unlink(name)
508 except os.error:
509 pass
510 try:
511 f = open(name, "w")
512 except IOError, msg:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000513 self.error("Can't open", name, "for writing:", cgi.escape(str(msg)))
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000514 return
515 now = time.ctime(time.time())
516 f.write("Title: %s\n" % title)
517 f.write("Last-Changed-Date: %s\n" % now)
518 f.write("Last-Changed-Author: %s\n" % author)
519 f.write("Last-Changed-Email: %s\n" % email)
520 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
521 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
522 keys = headers.keys()
523 keys.sort()
524 keys.remove('title')
525 for key in keys:
526 if key[:13] != 'last-changed-':
527 f.write("%s: %s\n" % (string.capwords(key, '-'),
528 headers[key]))
529 f.write("\n")
530 f.write(text)
531 f.write("\n")
532 f.close()
533
534 tfn = tempfile.mktemp()
535 f = open(tfn, "w")
536 f.write("Last-Changed-Date: %s\n" % now)
537 f.write("Last-Changed-Author: %s\n" % author)
538 f.write("Last-Changed-Email: %s\n" % email)
539 f.write("Last-Changed-Remote-Host: %s\n" % remhost)
540 f.write("Last-Changed-Remote-Address: %s\n" % remaddr)
541 f.write("\n")
542 f.write(log)
543 f.write("\n")
544 f.close()
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000545
546 # Do this for show() below
547 self.headers = {
548 'title': title,
549 'last-changed-date': now,
550 'last-changed-author': author,
551 'last-changed-email': email,
552 'last-changed-remote-host': remhost,
553 'last-changed-remote-address': remaddr,
554 }
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000555
556 p = os.popen("""
557 /depot/gnu/plat/bin/rcs -l %s </dev/null 2>&1
558 /depot/gnu/plat/bin/ci -u %s <%s 2>&1
559 rm -f %s
560 """ % (name, name, tfn, tfn))
561 output = p.read()
562 sts = p.close()
563 if not sts:
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000564 self.set_cookie(author, email, password)
Guido van Rossum74427e51997-05-21 23:43:39 +0000565 self.prologue("Python FAQ Entry Edited")
566 print "<HR>"
Guido van Rossumed531fd1997-05-22 15:21:57 +0000567 self.show(name, title, text)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000568 if output:
569 print "<PRE>%s</PRE>" % cgi.escape(output)
570 else:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000571 self.error("Python FAQ Entry Commit Failed",
572 "Exit status 0x%04x" % sts)
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000573 if output:
574 print "<PRE>%s</PRE>" % cgi.escape(output)
575
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000576 def set_cookie(self, author, email, password):
Guido van Rossum9c6ceda1997-05-23 22:44:01 +0000577 name = "Python-FAQ-Wizard"
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000578 value = "%s/%s/%s" % (author, email, password)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000579 import urllib
580 value = urllib.quote(value)
581 print "Set-Cookie: %s=%s; path=/cgi-bin/;" % (name, value),
Guido van Rossum1d579811997-05-23 19:18:35 +0000582 import time
583 now = time.time()
584 then = now + 28 * 24 * 3600
585 gmt = time.gmtime(then)
586 print time.strftime("expires=%a, %d-%b-%x %X GMT", gmt)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000587
588 def get_cookie(self):
589 if not os.environ.has_key('HTTP_COOKIE'):
Guido van Rossum9c6ceda1997-05-23 22:44:01 +0000590 return "", "", ""
Guido van Rossumaf5be951997-05-22 16:57:50 +0000591 raw = os.environ['HTTP_COOKIE']
Guido van Rossum9c6ceda1997-05-23 22:44:01 +0000592 words = map(string.strip, string.split(raw, ';'))
Guido van Rossumaf5be951997-05-22 16:57:50 +0000593 cookies = {}
594 for word in words:
595 i = string.find(word, '=')
596 if i >= 0:
597 key, value = word[:i], word[i+1:]
598 cookies[key] = value
Guido van Rossum9c6ceda1997-05-23 22:44:01 +0000599 if not cookies.has_key('Python-FAQ-Wizard'):
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000600 return "", "", ""
Guido van Rossum9c6ceda1997-05-23 22:44:01 +0000601 value = cookies['Python-FAQ-Wizard']
Guido van Rossumaf5be951997-05-22 16:57:50 +0000602 import urllib
603 value = urllib.unquote(value)
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000604 words = string.split(value, '/')
605 while len(words) < 3:
606 words.append('')
607 author = string.join(words[:-2], '/')
608 email = words[-2]
609 password = words[-1]
610 return author, email, password
Guido van Rossumaf5be951997-05-22 16:57:50 +0000611
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000612 def showedit(self, name, title, text):
Guido van Rossumaf5be951997-05-22 16:57:50 +0000613 author = self.author
614 email = self.email
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000615 password = self.password
Guido van Rossum9c6ceda1997-05-23 22:44:01 +0000616 if not author or not email or not password:
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000617 a, e, p = self.get_cookie()
Guido van Rossumaf5be951997-05-22 16:57:50 +0000618 author = author or a
619 email = email or e
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000620 password = password or p
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000621 print """
Guido van Rossumed531fd1997-05-22 15:21:57 +0000622 Title: <INPUT TYPE=text SIZE=70 NAME=title VALUE="%s"><BR>
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000623 <TEXTAREA COLS=80 ROWS=20 NAME=text>%s\n</TEXTAREA>""" % (
624 self.escape(title), cgi.escape(string.strip(text)))
625 print """<BR>
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000626 Log message (reason for the change):<BR>
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000627 <TEXTAREA COLS=80 ROWS=5 NAME=log>%s\n</TEXTAREA><BR>
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000628 Please provide the following information for logging purposes:
Guido van Rossuma78a3c31997-05-23 22:29:24 +0000629 <TABLE FRAME=none COLS=2>
630 <TR>
631 <TD>Name:
632 <TD><INPUT TYPE=text SIZE=40 NAME=author VALUE="%s">
633 <TR>
634 <TD>Email:
635 <TD><INPUT TYPE=text SIZE=40 NAME=email VALUE="%s">
636 <TR>
637 <TD>Password:
638 <TD><INPUT TYPE=password SIZE=40 NAME=password VALUE="%s">
639 </TABLE>
640 """ % (self.escape(self.log), self.escape(author),
641 self.escape(email), self.escape(password))
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000642
643 def escape(self, s):
644 import regsub
645 if '&' in s:
646 s = regsub.gsub("&", "&amp;", s) # Must be done first!
647 if '<' in s:
648 s = regsub.gsub("<", "&lt;", s)
649 if '>' in s:
650 s = regsub.gsub(">", "&gt;", s)
651 if '"' in s:
652 s = regsub.gsub('"', "&quot;", s)
653 return s
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000654
655 def showheaders(self, headers):
656 print "<UL>"
657 keys = map(string.lower, headers.keys())
658 keys.sort()
659 for key in keys:
660 print "<LI><B>%s:</B> %s" % (string.capwords(key, '-'),
661 headers[key] or '')
662 print "</UL>"
663
Guido van Rossumed531fd1997-05-22 15:21:57 +0000664 headers = None
665
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000666 def read(self, name):
Guido van Rossumed531fd1997-05-22 15:21:57 +0000667 self.headers = None
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000668 import fnmatch, rfc822
669 if not fnmatch.fnmatch(name, NAMEPAT):
670 return None, None
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000671 if self.add:
672 try:
673 fname = "faq%02d.%03d.htp" % (string.atoi(self.section),
674 string.atoi(self.number))
675 except ValueError:
676 return None, None
677 if fname != name:
678 return None, None
679 headers = {'title': "%s.%s. " % (self.section, self.number)}
680 text = ""
Guido van Rossumed531fd1997-05-22 15:21:57 +0000681 else:
682 f = open(name)
683 headers = rfc822.Message(f)
684 text = f.read()
685 f.close()
686 self.headers = headers
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000687 return headers, text
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000688
Guido van Rossumed531fd1997-05-22 15:21:57 +0000689 def show(self, name, title, text, edit=1):
Guido van Rossumd7bfa801997-05-21 21:31:39 +0000690 print "<H2>%s</H2>" % cgi.escape(title)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000691 pre = 0
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000692 for line in string.split(text, '\n'):
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000693 if not string.strip(line):
694 if pre:
695 print '</PRE>'
696 pre = 0
697 else:
698 print '<P>'
699 else:
Guido van Rossum5527db51997-05-23 04:44:30 +0000700 if line[0] not in string.whitespace:
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000701 if pre:
702 print '</PRE>'
703 pre = 0
704 else:
705 if not pre:
706 print '<PRE>'
707 pre = 1
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000708 if '/' in line or '@' in line:
709 line = self.translate(line)
Guido van Rossuma0e9a6d1997-05-23 18:13:58 +0000710 elif '<' in line or '&' in line:
711 line = cgi.escape(line)
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000712 if not pre and '*' in line:
713 line = self.emphasize(line)
714 print line
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000715 if pre:
716 print '</PRE>'
717 pre = 0
718 print '<P>'
719 if edit:
Guido van Rossumf701bf11997-05-21 22:25:56 +0000720 print """
721 <A HREF="faq.py?req=edit&name=%s">Edit this entry</A> /
Guido van Rossum1d579811997-05-23 19:18:35 +0000722 <A HREF="faq.py?req=info&name=%s" TARGET=rlog>Log info</A>
Guido van Rossumf701bf11997-05-21 22:25:56 +0000723 """ % (name, name)
Guido van Rossumed531fd1997-05-22 15:21:57 +0000724 if self.headers:
725 try:
726 date = self.headers['last-changed-date']
727 author = self.headers['last-changed-author']
728 email = self.headers['last-changed-email']
729 except KeyError:
730 pass
731 else:
Guido van Rossum1d579811997-05-23 19:18:35 +0000732 s = '/ Last changed on %s by <A HREF="mailto:%s">%s</A>'
Guido van Rossumed531fd1997-05-22 15:21:57 +0000733 print s % (date, email, author)
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000734 print '<P>'
735 print "<HR>"
736
Guido van Rossum74427e51997-05-21 23:43:39 +0000737 def getversion(self, name):
738 p = os.popen("/depot/gnu/plat/bin/rlog -h %s </dev/null 2>&1" % name)
Guido van Rossum64099e91997-05-22 15:49:23 +0000739 head = "*new*"
Guido van Rossum74427e51997-05-21 23:43:39 +0000740 while 1:
741 line = p.readline()
742 if not line:
743 break
744 if line[:5] == 'head:':
745 head = string.strip(line[5:])
746 p.close()
747 return head
748
749 def prologue(self, title):
750 title = cgi.escape(title)
Guido van Rossumaf5be951997-05-22 16:57:50 +0000751 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000752 <HTML>
753 <HEAD>
754 <TITLE>%s</TITLE>
755 </HEAD>
756 <BODY BACKGROUND="http://www.python.org/pics/RedShort.gif"
757 BGCOLOR="#FFFFFF"
758 TEXT="#000000"
759 LINK="#AA0000"
760 VLINK="#906A6A">
761 <H1>%s</H1>
762 ''' % (title, title)
763
Guido van Rossumaf5be951997-05-22 16:57:50 +0000764 def error(self, *messages):
765 self.prologue("Python FAQ error")
766 print "Sorry, an error occurred:<BR>"
767 for message in messages:
768 print message,
769 print
770
Guido van Rossum74427e51997-05-21 23:43:39 +0000771 def epilogue(self):
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000772 if self.edit == 'no':
773 global wanttime
774 wanttime = 0
775 else:
776 print '''
777 <P>
778 <HR>
779 <A HREF="http://www.python.org">Python home</A> /
Guido van Rossumefe640c1997-05-23 23:07:44 +0000780 <A HREF="faq.py?req=frontpage">FAQ Wizard home</A> /
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000781 Feedback to <A HREF="mailto:guido@python.org">GvR</A>
782 '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000783 print '''
Guido van Rossum74427e51997-05-21 23:43:39 +0000784 </BODY>
785 </HTML>
786 '''
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000787
Guido van Rossum5527db51997-05-23 04:44:30 +0000788 translate_prog = None
789
790 def translate(self, text):
791 if not self.translate_prog:
792 import regex
793 url = '\(http\|ftp\)://[^ \t\r\n]*'
794 email = '\<[-a-zA-Z0-9._]+@[-a-zA-Z0-9._]+'
795 self.translate_prog = prog = regex.compile(url + "\|" + email)
796 else:
797 prog = self.translate_prog
798 i = 0
799 list = []
800 while 1:
801 j = prog.search(text, i)
802 if j < 0:
803 break
804 list.append(cgi.escape(text[i:j]))
805 i = j
806 url = prog.group(0)
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000807 while url[-1] in ");:,.?'\"":
Guido van Rossum5527db51997-05-23 04:44:30 +0000808 url = url[:-1]
Guido van Rossum1dcc2441997-05-23 18:53:06 +0000809 url = self.escape(url)
Guido van Rossum5527db51997-05-23 04:44:30 +0000810 if ':' in url:
811 repl = '<A HREF="%s">%s</A>' % (url, url)
812 else:
813 repl = '<A HREF="mailto:%s">&lt;%s&gt;</A>' % (url, url)
814 list.append(repl)
815 i = i + len(url)
816 j = len(text)
817 list.append(cgi.escape(text[i:j]))
818 return string.join(list, '')
819
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000820 emphasize_prog = None
821
822 def emphasize(self, line):
823 import regsub
824 if not self.emphasize_prog:
825 import regex
826 pat = "\*\([a-zA-Z]+\)\*"
827 self.emphasize_prog = prog = regex.compile(pat)
828 else:
829 prog = self.emphasize_prog
830 return regsub.gsub(prog, "<I>\\1</I>", line)
831
Guido van Rossumaf5be951997-05-22 16:57:50 +0000832print "Content-type: text/html"
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000833dt = 0
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000834wanttime = 0
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000835try:
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000836 import time
837 t1 = time.time()
Guido van Rossum74427e51997-05-21 23:43:39 +0000838 import cgi, string, os, sys
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000839 x = FAQServer()
840 x.main()
Guido van Rossum3c3354c1997-05-21 16:52:18 +0000841 t2 = time.time()
842 dt = t2-t1
Guido van Rossumf8f0fb71997-05-23 21:21:43 +0000843 wanttime = 1
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000844except:
Guido van Rossumaf5be951997-05-22 16:57:50 +0000845 print "\n<HR>Sorry, an error occurred"
Guido van Rossumadb3a9d1997-05-21 07:24:50 +0000846 cgi.print_exception()
Guido van Rossumd1c1ec81997-05-23 17:45:04 +0000847if wanttime:
848 print "<BR>(running time = %s seconds)" % str(round(dt, 3))
Guido van Rossumed531fd1997-05-22 15:21:57 +0000849
850# The following bootstrap script must be placed in cgi-bin/faq.py:
851BOOTSTRAP = """
852#! /usr/local/bin/python
853FAQDIR = "/usr/people/guido/python/FAQ"
854import os, sys
855os.chdir(FAQDIR)
856sys.path.insert(0, os.curdir)
857import faqmain
858"""