~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Tools/faqwiz/faqwiz.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Generic FAQ Wizard.
 
2
 
 
3
This is a CGI program that maintains a user-editable FAQ.  It uses RCS
 
4
to keep track of changes to individual FAQ entries.  It is fully
 
5
configurable; everything you might want to change when using this
 
6
program to maintain some other FAQ than the Python FAQ is contained in
 
7
the configuration module, faqconf.py.
 
8
 
 
9
Note that this is not an executable script; it's an importable module.
 
10
The actual script to place in cgi-bin is faqw.py.
 
11
 
 
12
"""
 
13
 
 
14
import sys, time, os, stat, re, cgi, faqconf
 
15
from faqconf import *                   # This imports all uppercase names
 
16
now = time.time()
 
17
 
 
18
class FileError:
 
19
    def __init__(self, file):
 
20
        self.file = file
 
21
 
 
22
class InvalidFile(FileError):
 
23
    pass
 
24
 
 
25
class NoSuchSection(FileError):
 
26
    def __init__(self, section):
 
27
        FileError.__init__(self, NEWFILENAME %(section, 1))
 
28
        self.section = section
 
29
 
 
30
class NoSuchFile(FileError):
 
31
    def __init__(self, file, why=None):
 
32
        FileError.__init__(self, file)
 
33
        self.why = why
 
34
 
 
35
def escape(s):
 
36
    s = s.replace('&', '&')
 
37
    s = s.replace('<', '&lt;')
 
38
    s = s.replace('>', '&gt;')
 
39
    return s
 
40
 
 
41
def escapeq(s):
 
42
    s = escape(s)
 
43
    s = s.replace('"', '&quot;')
 
44
    return s
 
45
 
 
46
def _interpolate(format, args, kw):
 
47
    try:
 
48
        quote = kw['_quote']
 
49
    except KeyError:
 
50
        quote = 1
 
51
    d = (kw,) + args + (faqconf.__dict__,)
 
52
    m = MagicDict(d, quote)
 
53
    return format % m
 
54
 
 
55
def interpolate(format, *args, **kw):
 
56
    return _interpolate(format, args, kw)
 
57
 
 
58
def emit(format, *args, **kw):
 
59
    try:
 
60
        f = kw['_file']
 
61
    except KeyError:
 
62
        f = sys.stdout
 
63
    f.write(_interpolate(format, args, kw))
 
64
 
 
65
translate_prog = None
 
66
 
 
67
def translate(text, pre=0):
 
68
    global translate_prog
 
69
    if not translate_prog:
 
70
        translate_prog = prog = re.compile(
 
71
            r'\b(http|ftp|https)://\S+(\b|/)|\b[-.\w]+@[-.\w]+')
 
72
    else:
 
73
        prog = translate_prog
 
74
    i = 0
 
75
    list = []
 
76
    while 1:
 
77
        m = prog.search(text, i)
 
78
        if not m:
 
79
            break
 
80
        j = m.start()
 
81
        list.append(escape(text[i:j]))
 
82
        i = j
 
83
        url = m.group(0)
 
84
        while url[-1] in '();:,.?\'"<>':
 
85
            url = url[:-1]
 
86
        i = i + len(url)
 
87
        url = escape(url)
 
88
        if not pre or (pre and PROCESS_PREFORMAT):
 
89
            if ':' in url:
 
90
                repl = '<A HREF="%s">%s</A>' % (url, url)
 
91
            else:
 
92
                repl = '<A HREF="mailto:%s">%s</A>' % (url, url)
 
93
        else:
 
94
            repl = url
 
95
        list.append(repl)
 
96
    j = len(text)
 
97
    list.append(escape(text[i:j]))
 
98
    return ''.join(list)
 
99
 
 
100
def emphasize(line):
 
101
    return re.sub(r'\*([a-zA-Z]+)\*', r'<I>\1</I>', line)
 
102
 
 
103
revparse_prog = None
 
104
 
 
105
def revparse(rev):
 
106
    global revparse_prog
 
107
    if not revparse_prog:
 
108
        revparse_prog = re.compile(r'^(\d{1,3})\.(\d{1,4})$')
 
109
    m = revparse_prog.match(rev)
 
110
    if not m:
 
111
        return None
 
112
    [major, minor] = map(int, m.group(1, 2))
 
113
    return major, minor
 
114
 
 
115
logon = 0
 
116
def log(text):
 
117
    if logon:
 
118
        logfile = open("logfile", "a")
 
119
        logfile.write(text + "\n")
 
120
        logfile.close()
 
121
 
 
122
def load_cookies():
 
123
    if 'HTTP_COOKIE' not in os.environ:
 
124
        return {}
 
125
    raw = os.environ['HTTP_COOKIE']
 
126
    words = [s.strip() for s in raw.split(';')]
 
127
    cookies = {}
 
128
    for word in words:
 
129
        i = word.find('=')
 
130
        if i >= 0:
 
131
            key, value = word[:i], word[i+1:]
 
132
            cookies[key] = value
 
133
    return cookies
 
134
 
 
135
def load_my_cookie():
 
136
    cookies = load_cookies()
 
137
    try:
 
138
        value = cookies[COOKIE_NAME]
 
139
    except KeyError:
 
140
        return {}
 
141
    import urllib.parse
 
142
    value = urllib.parse.unquote(value)
 
143
    words = value.split('/')
 
144
    while len(words) < 3:
 
145
        words.append('')
 
146
    author = '/'.join(words[:-2])
 
147
    email = words[-2]
 
148
    password = words[-1]
 
149
    return {'author': author,
 
150
            'email': email,
 
151
            'password': password}
 
152
 
 
153
def send_my_cookie(ui):
 
154
    name = COOKIE_NAME
 
155
    value = "%s/%s/%s" % (ui.author, ui.email, ui.password)
 
156
    import urllib.parse
 
157
    value = urllib.parse.quote(value)
 
158
    then = now + COOKIE_LIFETIME
 
159
    gmt = time.gmtime(then)
 
160
    path = os.environ.get('SCRIPT_NAME', '/cgi-bin/')
 
161
    print("Set-Cookie: %s=%s; path=%s;" % (name, value, path), end=' ')
 
162
    print(time.strftime("expires=%a, %d-%b-%y %X GMT", gmt))
 
163
 
 
164
class MagicDict:
 
165
 
 
166
    def __init__(self, d, quote):
 
167
        self.__d = d
 
168
        self.__quote = quote
 
169
 
 
170
    def __getitem__(self, key):
 
171
        for d in self.__d:
 
172
            try:
 
173
                value = d[key]
 
174
                if value:
 
175
                    value = str(value)
 
176
                    if self.__quote:
 
177
                        value = escapeq(value)
 
178
                    return value
 
179
            except KeyError:
 
180
                pass
 
181
        return ''
 
182
 
 
183
class UserInput:
 
184
 
 
185
    def __init__(self):
 
186
        self.__form = cgi.FieldStorage()
 
187
        #log("\n\nbody: " + self.body)
 
188
 
 
189
    def __getattr__(self, name):
 
190
        if name[0] == '_':
 
191
            raise AttributeError
 
192
        try:
 
193
            value = self.__form[name].value
 
194
        except (TypeError, KeyError):
 
195
            value = ''
 
196
        else:
 
197
            value = value.strip()
 
198
        setattr(self, name, value)
 
199
        return value
 
200
 
 
201
    def __getitem__(self, key):
 
202
        return getattr(self, key)
 
203
 
 
204
class FaqEntry:
 
205
 
 
206
    def __init__(self, fp, file, sec_num):
 
207
        self.file = file
 
208
        self.sec, self.num = sec_num
 
209
        if fp:
 
210
            import email
 
211
            self.__headers = email.message_from_file(fp)
 
212
            self.body = fp.read().strip()
 
213
        else:
 
214
            self.__headers = {'title': "%d.%d. " % sec_num}
 
215
            self.body = ''
 
216
 
 
217
    def __getattr__(self, name):
 
218
        if name[0] == '_':
 
219
            raise AttributeError
 
220
        key = '-'.join(name.split('_'))
 
221
        try:
 
222
            value = self.__headers[key]
 
223
        except KeyError:
 
224
            value = ''
 
225
        setattr(self, name, value)
 
226
        return value
 
227
 
 
228
    def __getitem__(self, key):
 
229
        return getattr(self, key)
 
230
 
 
231
    def load_version(self):
 
232
        command = interpolate(SH_RLOG_H, self)
 
233
        p = os.popen(command)
 
234
        version = ''
 
235
        while 1:
 
236
            line = p.readline()
 
237
            if not line:
 
238
                break
 
239
            if line[:5] == 'head:':
 
240
                version = line[5:].strip()
 
241
        p.close()
 
242
        self.version = version
 
243
 
 
244
    def getmtime(self):
 
245
        if not self.last_changed_date:
 
246
            return 0
 
247
        try:
 
248
            return os.stat(self.file)[stat.ST_MTIME]
 
249
        except os.error:
 
250
            return 0
 
251
 
 
252
    def emit_marks(self):
 
253
        mtime = self.getmtime()
 
254
        if mtime >= now - DT_VERY_RECENT:
 
255
            emit(MARK_VERY_RECENT, self)
 
256
        elif mtime >= now - DT_RECENT:
 
257
            emit(MARK_RECENT, self)
 
258
 
 
259
    def show(self, edit=1):
 
260
        emit(ENTRY_HEADER1, self)
 
261
        self.emit_marks()
 
262
        emit(ENTRY_HEADER2, self)
 
263
        pre = 0
 
264
        raw = 0
 
265
        for line in self.body.split('\n'):
 
266
            # Allow the user to insert raw html into a FAQ answer
 
267
            # (Skip Montanaro, with changes by Guido)
 
268
            tag = line.rstrip().lower()
 
269
            if tag == '<html>':
 
270
                raw = 1
 
271
                continue
 
272
            if tag == '</html>':
 
273
                raw = 0
 
274
                continue
 
275
            if raw:
 
276
                print(line)
 
277
                continue
 
278
            if not line.strip():
 
279
                if pre:
 
280
                    print('</PRE>')
 
281
                    pre = 0
 
282
                else:
 
283
                    print('<P>')
 
284
            else:
 
285
                if not line[0].isspace():
 
286
                    if pre:
 
287
                        print('</PRE>')
 
288
                        pre = 0
 
289
                else:
 
290
                    if not pre:
 
291
                        print('<PRE>')
 
292
                        pre = 1
 
293
                if '/' in line or '@' in line:
 
294
                    line = translate(line, pre)
 
295
                elif '<' in line or '&' in line:
 
296
                    line = escape(line)
 
297
                if not pre and '*' in line:
 
298
                    line = emphasize(line)
 
299
                print(line)
 
300
        if pre:
 
301
            print('</PRE>')
 
302
            pre = 0
 
303
        if edit:
 
304
            print('<P>')
 
305
            emit(ENTRY_FOOTER, self)
 
306
            if self.last_changed_date:
 
307
                emit(ENTRY_LOGINFO, self)
 
308
        print('<P>')
 
309
 
 
310
class FaqDir:
 
311
 
 
312
    entryclass = FaqEntry
 
313
 
 
314
    __okprog = re.compile(OKFILENAME)
 
315
 
 
316
    def __init__(self, dir=os.curdir):
 
317
        self.__dir = dir
 
318
        self.__files = None
 
319
 
 
320
    def __fill(self):
 
321
        if self.__files is not None:
 
322
            return
 
323
        self.__files = files = []
 
324
        okprog = self.__okprog
 
325
        for file in os.listdir(self.__dir):
 
326
            if self.__okprog.match(file):
 
327
                files.append(file)
 
328
        files.sort()
 
329
 
 
330
    def good(self, file):
 
331
        return self.__okprog.match(file)
 
332
 
 
333
    def parse(self, file):
 
334
        m = self.good(file)
 
335
        if not m:
 
336
            return None
 
337
        sec, num = m.group(1, 2)
 
338
        return int(sec), int(num)
 
339
 
 
340
    def list(self):
 
341
        # XXX Caller shouldn't modify result
 
342
        self.__fill()
 
343
        return self.__files
 
344
 
 
345
    def open(self, file):
 
346
        sec_num = self.parse(file)
 
347
        if not sec_num:
 
348
            raise InvalidFile(file)
 
349
        try:
 
350
            fp = open(file)
 
351
        except IOError as msg:
 
352
            raise NoSuchFile(file, msg)
 
353
        try:
 
354
            return self.entryclass(fp, file, sec_num)
 
355
        finally:
 
356
            fp.close()
 
357
 
 
358
    def show(self, file, edit=1):
 
359
        self.open(file).show(edit=edit)
 
360
 
 
361
    def new(self, section):
 
362
        if section not in SECTION_TITLES:
 
363
            raise NoSuchSection(section)
 
364
        maxnum = 0
 
365
        for file in self.list():
 
366
            sec, num = self.parse(file)
 
367
            if sec == section:
 
368
                maxnum = max(maxnum, num)
 
369
        sec_num = (section, maxnum+1)
 
370
        file = NEWFILENAME % sec_num
 
371
        return self.entryclass(None, file, sec_num)
 
372
 
 
373
class FaqWizard:
 
374
 
 
375
    def __init__(self):
 
376
        self.ui = UserInput()
 
377
        self.dir = FaqDir()
 
378
 
 
379
    def go(self):
 
380
        print('Content-type: text/html')
 
381
        req = self.ui.req or 'home'
 
382
        mname = 'do_%s' % req
 
383
        try:
 
384
            meth = getattr(self, mname)
 
385
        except AttributeError:
 
386
            self.error("Bad request type %r." % (req,))
 
387
        else:
 
388
            try:
 
389
                meth()
 
390
            except InvalidFile as exc:
 
391
                self.error("Invalid entry file name %s" % exc.file)
 
392
            except NoSuchFile as exc:
 
393
                self.error("No entry with file name %s" % exc.file)
 
394
            except NoSuchSection as exc:
 
395
                self.error("No section number %s" % exc.section)
 
396
        self.epilogue()
 
397
 
 
398
    def error(self, message, **kw):
 
399
        self.prologue(T_ERROR)
 
400
        emit(message, kw)
 
401
 
 
402
    def prologue(self, title, entry=None, **kw):
 
403
        emit(PROLOGUE, entry, kwdict=kw, title=escape(title))
 
404
 
 
405
    def epilogue(self):
 
406
        emit(EPILOGUE)
 
407
 
 
408
    def do_home(self):
 
409
        self.prologue(T_HOME)
 
410
        emit(HOME)
 
411
 
 
412
    def do_debug(self):
 
413
        self.prologue("FAQ Wizard Debugging")
 
414
        form = cgi.FieldStorage()
 
415
        cgi.print_form(form)
 
416
        cgi.print_environ(os.environ)
 
417
        cgi.print_directory()
 
418
        cgi.print_arguments()
 
419
 
 
420
    def do_search(self):
 
421
        query = self.ui.query
 
422
        if not query:
 
423
            self.error("Empty query string!")
 
424
            return
 
425
        if self.ui.querytype == 'simple':
 
426
            query = re.escape(query)
 
427
            queries = [query]
 
428
        elif self.ui.querytype in ('anykeywords', 'allkeywords'):
 
429
            words = [_f for _f in re.split('\W+', query) if _f]
 
430
            if not words:
 
431
                self.error("No keywords specified!")
 
432
                return
 
433
            words = [r'\b%s\b' % w for w in words]
 
434
            if self.ui.querytype[:3] == 'any':
 
435
                queries = ['|'.join(words)]
 
436
            else:
 
437
                # Each of the individual queries must match
 
438
                queries = words
 
439
        else:
 
440
            # Default to regular expression
 
441
            queries = [query]
 
442
        self.prologue(T_SEARCH)
 
443
        progs = []
 
444
        for query in queries:
 
445
            if self.ui.casefold == 'no':
 
446
                p = re.compile(query)
 
447
            else:
 
448
                p = re.compile(query, re.IGNORECASE)
 
449
            progs.append(p)
 
450
        hits = []
 
451
        for file in self.dir.list():
 
452
            try:
 
453
                entry = self.dir.open(file)
 
454
            except FileError:
 
455
                constants
 
456
            for p in progs:
 
457
                if not p.search(entry.title) and not p.search(entry.body):
 
458
                    break
 
459
            else:
 
460
                hits.append(file)
 
461
        if not hits:
 
462
            emit(NO_HITS, self.ui, count=0)
 
463
        elif len(hits) <= MAXHITS:
 
464
            if len(hits) == 1:
 
465
                emit(ONE_HIT, count=1)
 
466
            else:
 
467
                emit(FEW_HITS, count=len(hits))
 
468
            self.format_all(hits, headers=0)
 
469
        else:
 
470
            emit(MANY_HITS, count=len(hits))
 
471
            self.format_index(hits)
 
472
 
 
473
    def do_all(self):
 
474
        self.prologue(T_ALL)
 
475
        files = self.dir.list()
 
476
        self.last_changed(files)
 
477
        self.format_index(files, localrefs=1)
 
478
        self.format_all(files)
 
479
 
 
480
    def do_compat(self):
 
481
        files = self.dir.list()
 
482
        emit(COMPAT)
 
483
        self.last_changed(files)
 
484
        self.format_index(files, localrefs=1)
 
485
        self.format_all(files, edit=0)
 
486
        sys.exit(0)                     # XXX Hack to suppress epilogue
 
487
 
 
488
    def last_changed(self, files):
 
489
        latest = 0
 
490
        for file in files:
 
491
            entry = self.dir.open(file)
 
492
            if entry:
 
493
                mtime = mtime = entry.getmtime()
 
494
                if mtime > latest:
 
495
                    latest = mtime
 
496
        print(time.strftime(LAST_CHANGED, time.localtime(latest)))
 
497
        emit(EXPLAIN_MARKS)
 
498
 
 
499
    def format_all(self, files, edit=1, headers=1):
 
500
        sec = 0
 
501
        for file in files:
 
502
            try:
 
503
                entry = self.dir.open(file)
 
504
            except NoSuchFile:
 
505
                continue
 
506
            if headers and entry.sec != sec:
 
507
                sec = entry.sec
 
508
                try:
 
509
                    title = SECTION_TITLES[sec]
 
510
                except KeyError:
 
511
                    title = "Untitled"
 
512
                emit("\n<HR>\n<H1>%(sec)s. %(title)s</H1>\n",
 
513
                     sec=sec, title=title)
 
514
            entry.show(edit=edit)
 
515
 
 
516
    def do_index(self):
 
517
        self.prologue(T_INDEX)
 
518
        files = self.dir.list()
 
519
        self.last_changed(files)
 
520
        self.format_index(files, add=1)
 
521
 
 
522
    def format_index(self, files, add=0, localrefs=0):
 
523
        sec = 0
 
524
        for file in files:
 
525
            try:
 
526
                entry = self.dir.open(file)
 
527
            except NoSuchFile:
 
528
                continue
 
529
            if entry.sec != sec:
 
530
                if sec:
 
531
                    if add:
 
532
                        emit(INDEX_ADDSECTION, sec=sec)
 
533
                    emit(INDEX_ENDSECTION, sec=sec)
 
534
                sec = entry.sec
 
535
                try:
 
536
                    title = SECTION_TITLES[sec]
 
537
                except KeyError:
 
538
                    title = "Untitled"
 
539
                emit(INDEX_SECTION, sec=sec, title=title)
 
540
            if localrefs:
 
541
                emit(LOCAL_ENTRY, entry)
 
542
            else:
 
543
                emit(INDEX_ENTRY, entry)
 
544
            entry.emit_marks()
 
545
        if sec:
 
546
            if add:
 
547
                emit(INDEX_ADDSECTION, sec=sec)
 
548
            emit(INDEX_ENDSECTION, sec=sec)
 
549
 
 
550
    def do_recent(self):
 
551
        if not self.ui.days:
 
552
            days = 1
 
553
        else:
 
554
            days = float(self.ui.days)
 
555
        try:
 
556
            cutoff = now - days * 24 * 3600
 
557
        except OverflowError:
 
558
            cutoff = 0
 
559
        list = []
 
560
        for file in self.dir.list():
 
561
            entry = self.dir.open(file)
 
562
            if not entry:
 
563
                continue
 
564
            mtime = entry.getmtime()
 
565
            if mtime >= cutoff:
 
566
                list.append((mtime, file))
 
567
        list.sort()
 
568
        list.reverse()
 
569
        self.prologue(T_RECENT)
 
570
        if days <= 1:
 
571
            period = "%.2g hours" % (days*24)
 
572
        else:
 
573
            period = "%.6g days" % days
 
574
        if not list:
 
575
            emit(NO_RECENT, period=period)
 
576
        elif len(list) == 1:
 
577
            emit(ONE_RECENT, period=period)
 
578
        else:
 
579
            emit(SOME_RECENT, period=period, count=len(list))
 
580
        self.format_all([mtime_file[1] for mtime_file in list], headers=0)
 
581
        emit(TAIL_RECENT)
 
582
 
 
583
    def do_roulette(self):
 
584
        import random
 
585
        files = self.dir.list()
 
586
        if not files:
 
587
            self.error("No entries.")
 
588
            return
 
589
        file = random.choice(files)
 
590
        self.prologue(T_ROULETTE)
 
591
        emit(ROULETTE)
 
592
        self.dir.show(file)
 
593
 
 
594
    def do_help(self):
 
595
        self.prologue(T_HELP)
 
596
        emit(HELP)
 
597
 
 
598
    def do_show(self):
 
599
        entry = self.dir.open(self.ui.file)
 
600
        self.prologue(T_SHOW)
 
601
        entry.show()
 
602
 
 
603
    def do_add(self):
 
604
        self.prologue(T_ADD)
 
605
        emit(ADD_HEAD)
 
606
        sections = sorted(SECTION_TITLES.items())
 
607
        for section, title in sections:
 
608
            emit(ADD_SECTION, section=section, title=title)
 
609
        emit(ADD_TAIL)
 
610
 
 
611
    def do_delete(self):
 
612
        self.prologue(T_DELETE)
 
613
        emit(DELETE)
 
614
 
 
615
    def do_log(self):
 
616
        entry = self.dir.open(self.ui.file)
 
617
        self.prologue(T_LOG, entry)
 
618
        emit(LOG, entry)
 
619
        self.rlog(interpolate(SH_RLOG, entry), entry)
 
620
 
 
621
    def rlog(self, command, entry=None):
 
622
        output = os.popen(command).read()
 
623
        sys.stdout.write('<PRE>')
 
624
        athead = 0
 
625
        lines = output.split('\n')
 
626
        while lines and not lines[-1]:
 
627
            del lines[-1]
 
628
        if lines:
 
629
            line = lines[-1]
 
630
            if line[:1] == '=' and len(line) >= 40 and \
 
631
               line == line[0]*len(line):
 
632
                del lines[-1]
 
633
        headrev = None
 
634
        for line in lines:
 
635
            if entry and athead and line[:9] == 'revision ':
 
636
                rev = line[9:].split()
 
637
                mami = revparse(rev)
 
638
                if not mami:
 
639
                    print(line)
 
640
                else:
 
641
                    emit(REVISIONLINK, entry, rev=rev, line=line)
 
642
                    if mami[1] > 1:
 
643
                        prev = "%d.%d" % (mami[0], mami[1]-1)
 
644
                        emit(DIFFLINK, entry, prev=prev, rev=rev)
 
645
                    if headrev:
 
646
                        emit(DIFFLINK, entry, prev=rev, rev=headrev)
 
647
                    else:
 
648
                        headrev = rev
 
649
                    print()
 
650
                athead = 0
 
651
            else:
 
652
                athead = 0
 
653
                if line[:1] == '-' and len(line) >= 20 and \
 
654
                   line == len(line) * line[0]:
 
655
                    athead = 1
 
656
                    sys.stdout.write('<HR>')
 
657
                else:
 
658
                    print(line)
 
659
        print('</PRE>')
 
660
 
 
661
    def do_revision(self):
 
662
        entry = self.dir.open(self.ui.file)
 
663
        rev = self.ui.rev
 
664
        mami = revparse(rev)
 
665
        if not mami:
 
666
            self.error("Invalid revision number: %r." % (rev,))
 
667
        self.prologue(T_REVISION, entry)
 
668
        self.shell(interpolate(SH_REVISION, entry, rev=rev))
 
669
 
 
670
    def do_diff(self):
 
671
        entry = self.dir.open(self.ui.file)
 
672
        prev = self.ui.prev
 
673
        rev = self.ui.rev
 
674
        mami = revparse(rev)
 
675
        if not mami:
 
676
            self.error("Invalid revision number: %r." % (rev,))
 
677
        if prev:
 
678
            if not revparse(prev):
 
679
                self.error("Invalid previous revision number: %r." % (prev,))
 
680
        else:
 
681
            prev = '%d.%d' % (mami[0], mami[1])
 
682
        self.prologue(T_DIFF, entry)
 
683
        self.shell(interpolate(SH_RDIFF, entry, rev=rev, prev=prev))
 
684
 
 
685
    def shell(self, command):
 
686
        output = os.popen(command).read()
 
687
        sys.stdout.write('<PRE>')
 
688
        print(escape(output))
 
689
        print('</PRE>')
 
690
 
 
691
    def do_new(self):
 
692
        entry = self.dir.new(section=int(self.ui.section))
 
693
        entry.version = '*new*'
 
694
        self.prologue(T_EDIT)
 
695
        emit(EDITHEAD)
 
696
        emit(EDITFORM1, entry, editversion=entry.version)
 
697
        emit(EDITFORM2, entry, load_my_cookie())
 
698
        emit(EDITFORM3)
 
699
        entry.show(edit=0)
 
700
 
 
701
    def do_edit(self):
 
702
        entry = self.dir.open(self.ui.file)
 
703
        entry.load_version()
 
704
        self.prologue(T_EDIT)
 
705
        emit(EDITHEAD)
 
706
        emit(EDITFORM1, entry, editversion=entry.version)
 
707
        emit(EDITFORM2, entry, load_my_cookie())
 
708
        emit(EDITFORM3)
 
709
        entry.show(edit=0)
 
710
 
 
711
    def do_review(self):
 
712
        send_my_cookie(self.ui)
 
713
        if self.ui.editversion == '*new*':
 
714
            sec, num = self.dir.parse(self.ui.file)
 
715
            entry = self.dir.new(section=sec)
 
716
            entry.version = "*new*"
 
717
            if entry.file != self.ui.file:
 
718
                self.error("Commit version conflict!")
 
719
                emit(NEWCONFLICT, self.ui, sec=sec, num=num)
 
720
                return
 
721
        else:
 
722
            entry = self.dir.open(self.ui.file)
 
723
            entry.load_version()
 
724
        # Check that the FAQ entry number didn't change
 
725
        if self.ui.title.split()[:1] != entry.title.split()[:1]:
 
726
            self.error("Don't change the entry number please!")
 
727
            return
 
728
        # Check that the edited version is the current version
 
729
        if entry.version != self.ui.editversion:
 
730
            self.error("Commit version conflict!")
 
731
            emit(VERSIONCONFLICT, entry, self.ui)
 
732
            return
 
733
        commit_ok = ((not PASSWORD
 
734
                      or self.ui.password == PASSWORD)
 
735
                     and self.ui.author
 
736
                     and '@' in self.ui.email
 
737
                     and self.ui.log)
 
738
        if self.ui.commit:
 
739
            if not commit_ok:
 
740
                self.cantcommit()
 
741
            else:
 
742
                self.commit(entry)
 
743
            return
 
744
        self.prologue(T_REVIEW)
 
745
        emit(REVIEWHEAD)
 
746
        entry.body = self.ui.body
 
747
        entry.title = self.ui.title
 
748
        entry.show(edit=0)
 
749
        emit(EDITFORM1, self.ui, entry)
 
750
        if commit_ok:
 
751
            emit(COMMIT)
 
752
        else:
 
753
            emit(NOCOMMIT_HEAD)
 
754
            self.errordetail()
 
755
            emit(NOCOMMIT_TAIL)
 
756
        emit(EDITFORM2, self.ui, entry, load_my_cookie())
 
757
        emit(EDITFORM3)
 
758
 
 
759
    def cantcommit(self):
 
760
        self.prologue(T_CANTCOMMIT)
 
761
        print(CANTCOMMIT_HEAD)
 
762
        self.errordetail()
 
763
        print(CANTCOMMIT_TAIL)
 
764
 
 
765
    def errordetail(self):
 
766
        if PASSWORD and self.ui.password != PASSWORD:
 
767
            emit(NEED_PASSWD)
 
768
        if not self.ui.log:
 
769
            emit(NEED_LOG)
 
770
        if not self.ui.author:
 
771
            emit(NEED_AUTHOR)
 
772
        if not self.ui.email:
 
773
            emit(NEED_EMAIL)
 
774
 
 
775
    def commit(self, entry):
 
776
        file = entry.file
 
777
        # Normalize line endings in body
 
778
        if '\r' in self.ui.body:
 
779
            self.ui.body = re.sub('\r\n?', '\n', self.ui.body)
 
780
        # Normalize whitespace in title
 
781
        self.ui.title = ' '.join(self.ui.title.split())
 
782
        # Check that there were any changes
 
783
        if self.ui.body == entry.body and self.ui.title == entry.title:
 
784
            self.error("You didn't make any changes!")
 
785
            return
 
786
 
 
787
        # need to lock here because otherwise the file exists and is not writable (on NT)
 
788
        command = interpolate(SH_LOCK, file=file)
 
789
        p = os.popen(command)
 
790
        output = p.read()
 
791
 
 
792
        try:
 
793
            os.unlink(file)
 
794
        except os.error:
 
795
            pass
 
796
        try:
 
797
            f = open(file, 'w')
 
798
        except IOError as why:
 
799
            self.error(CANTWRITE, file=file, why=why)
 
800
            return
 
801
        date = time.ctime(now)
 
802
        emit(FILEHEADER, self.ui, os.environ, date=date, _file=f, _quote=0)
 
803
        f.write('\n')
 
804
        f.write(self.ui.body)
 
805
        f.write('\n')
 
806
        f.close()
 
807
 
 
808
        import tempfile
 
809
        tf = tempfile.NamedTemporaryFile()
 
810
        emit(LOGHEADER, self.ui, os.environ, date=date, _file=tf)
 
811
        tf.flush()
 
812
        tf.seek(0)
 
813
 
 
814
        command = interpolate(SH_CHECKIN, file=file, tfn=tf.name)
 
815
        log("\n\n" + command)
 
816
        p = os.popen(command)
 
817
        output = p.read()
 
818
        sts = p.close()
 
819
        log("output: " + output)
 
820
        log("done: " + str(sts))
 
821
        log("TempFile:\n" + tf.read() + "end")
 
822
 
 
823
        if not sts:
 
824
            self.prologue(T_COMMITTED)
 
825
            emit(COMMITTED)
 
826
        else:
 
827
            self.error(T_COMMITFAILED)
 
828
            emit(COMMITFAILED, sts=sts)
 
829
        print('<PRE>%s</PRE>' % escape(output))
 
830
 
 
831
        try:
 
832
            os.unlink(tf.name)
 
833
        except os.error:
 
834
            pass
 
835
 
 
836
        entry = self.dir.open(file)
 
837
        entry.show()
 
838
 
 
839
wiz = FaqWizard()
 
840
wiz.go()