xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/calendar.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1"""Calendar printing functions
2
3Note when comparing these calendars to the ones printed by cal(1): By
4default, these calendars have Monday as the first day of the week, and
5Sunday as the last (the European convention). Use setfirstweekday() to
6set the first day of the week (0=Monday, 6=Sunday)."""
7
8import sys
9import datetime
10import locale as _locale
11from itertools import repeat
12
13__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
14           "firstweekday", "isleap", "leapdays", "weekday", "monthrange",
15           "monthcalendar", "prmonth", "month", "prcal", "calendar",
16           "timegm", "month_name", "month_abbr", "day_name", "day_abbr",
17           "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
18           "LocaleHTMLCalendar", "weekheader",
19           "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",
20           "SATURDAY", "SUNDAY"]
21
22# Exception raised for bad input (with string parameter for details)
23error = ValueError
24
25# Exceptions raised for bad input
26class IllegalMonthError(ValueError):
27    def __init__(self, month):
28        self.month = month
29    def __str__(self):
30        return "bad month number %r; must be 1-12" % self.month
31
32
33class IllegalWeekdayError(ValueError):
34    def __init__(self, weekday):
35        self.weekday = weekday
36    def __str__(self):
37        return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
38
39
40# Constants for months referenced later
41January = 1
42February = 2
43
44# Number of days per month (except for February in leap years)
45mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
46
47# This module used to have hard-coded lists of day and month names, as
48# English strings.  The classes following emulate a read-only version of
49# that, but supply localized names.  Note that the values are computed
50# fresh on each call, in case the user changes locale between calls.
51
52class _localized_month:
53
54    _months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
55    _months.insert(0, lambda x: "")
56
57    def __init__(self, format):
58        self.format = format
59
60    def __getitem__(self, i):
61        funcs = self._months[i]
62        if isinstance(i, slice):
63            return [f(self.format) for f in funcs]
64        else:
65            return funcs(self.format)
66
67    def __len__(self):
68        return 13
69
70
71class _localized_day:
72
73    # January 1, 2001, was a Monday.
74    _days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
75
76    def __init__(self, format):
77        self.format = format
78
79    def __getitem__(self, i):
80        funcs = self._days[i]
81        if isinstance(i, slice):
82            return [f(self.format) for f in funcs]
83        else:
84            return funcs(self.format)
85
86    def __len__(self):
87        return 7
88
89
90# Full and abbreviated names of weekdays
91day_name = _localized_day('%A')
92day_abbr = _localized_day('%a')
93
94# Full and abbreviated names of months (1-based arrays!!!)
95month_name = _localized_month('%B')
96month_abbr = _localized_month('%b')
97
98# Constants for weekdays
99(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
100
101
102def isleap(year):
103    """Return True for leap years, False for non-leap years."""
104    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
105
106
107def leapdays(y1, y2):
108    """Return number of leap years in range [y1, y2).
109       Assume y1 <= y2."""
110    y1 -= 1
111    y2 -= 1
112    return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
113
114
115def weekday(year, month, day):
116    """Return weekday (0-6 ~ Mon-Sun) for year, month (1-12), day (1-31)."""
117    if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
118        year = 2000 + year % 400
119    return datetime.date(year, month, day).weekday()
120
121
122def monthrange(year, month):
123    """Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
124       year, month."""
125    if not 1 <= month <= 12:
126        raise IllegalMonthError(month)
127    day1 = weekday(year, month, 1)
128    ndays = mdays[month] + (month == February and isleap(year))
129    return day1, ndays
130
131
132def _monthlen(year, month):
133    return mdays[month] + (month == February and isleap(year))
134
135
136def _prevmonth(year, month):
137    if month == 1:
138        return year-1, 12
139    else:
140        return year, month-1
141
142
143def _nextmonth(year, month):
144    if month == 12:
145        return year+1, 1
146    else:
147        return year, month+1
148
149
150class Calendar(object):
151    """
152    Base calendar class. This class doesn't do any formatting. It simply
153    provides data to subclasses.
154    """
155
156    def __init__(self, firstweekday=0):
157        self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
158
159    def getfirstweekday(self):
160        return self._firstweekday % 7
161
162    def setfirstweekday(self, firstweekday):
163        self._firstweekday = firstweekday
164
165    firstweekday = property(getfirstweekday, setfirstweekday)
166
167    def iterweekdays(self):
168        """
169        Return an iterator for one week of weekday numbers starting with the
170        configured first one.
171        """
172        for i in range(self.firstweekday, self.firstweekday + 7):
173            yield i%7
174
175    def itermonthdates(self, year, month):
176        """
177        Return an iterator for one month. The iterator will yield datetime.date
178        values and will always iterate through complete weeks, so it will yield
179        dates outside the specified month.
180        """
181        for y, m, d in self.itermonthdays3(year, month):
182            yield datetime.date(y, m, d)
183
184    def itermonthdays(self, year, month):
185        """
186        Like itermonthdates(), but will yield day numbers. For days outside
187        the specified month the day number is 0.
188        """
189        day1, ndays = monthrange(year, month)
190        days_before = (day1 - self.firstweekday) % 7
191        yield from repeat(0, days_before)
192        yield from range(1, ndays + 1)
193        days_after = (self.firstweekday - day1 - ndays) % 7
194        yield from repeat(0, days_after)
195
196    def itermonthdays2(self, year, month):
197        """
198        Like itermonthdates(), but will yield (day number, weekday number)
199        tuples. For days outside the specified month the day number is 0.
200        """
201        for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
202            yield d, i % 7
203
204    def itermonthdays3(self, year, month):
205        """
206        Like itermonthdates(), but will yield (year, month, day) tuples.  Can be
207        used for dates outside of datetime.date range.
208        """
209        day1, ndays = monthrange(year, month)
210        days_before = (day1 - self.firstweekday) % 7
211        days_after = (self.firstweekday - day1 - ndays) % 7
212        y, m = _prevmonth(year, month)
213        end = _monthlen(y, m) + 1
214        for d in range(end-days_before, end):
215            yield y, m, d
216        for d in range(1, ndays + 1):
217            yield year, month, d
218        y, m = _nextmonth(year, month)
219        for d in range(1, days_after + 1):
220            yield y, m, d
221
222    def itermonthdays4(self, year, month):
223        """
224        Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
225        Can be used for dates outside of datetime.date range.
226        """
227        for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
228            yield y, m, d, (self.firstweekday + i) % 7
229
230    def monthdatescalendar(self, year, month):
231        """
232        Return a matrix (list of lists) representing a month's calendar.
233        Each row represents a week; week entries are datetime.date values.
234        """
235        dates = list(self.itermonthdates(year, month))
236        return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
237
238    def monthdays2calendar(self, year, month):
239        """
240        Return a matrix representing a month's calendar.
241        Each row represents a week; week entries are
242        (day number, weekday number) tuples. Day numbers outside this month
243        are zero.
244        """
245        days = list(self.itermonthdays2(year, month))
246        return [ days[i:i+7] for i in range(0, len(days), 7) ]
247
248    def monthdayscalendar(self, year, month):
249        """
250        Return a matrix representing a month's calendar.
251        Each row represents a week; days outside this month are zero.
252        """
253        days = list(self.itermonthdays(year, month))
254        return [ days[i:i+7] for i in range(0, len(days), 7) ]
255
256    def yeardatescalendar(self, year, width=3):
257        """
258        Return the data for the specified year ready for formatting. The return
259        value is a list of month rows. Each month row contains up to width months.
260        Each month contains between 4 and 6 weeks and each week contains 1-7
261        days. Days are datetime.date objects.
262        """
263        months = [
264            self.monthdatescalendar(year, i)
265            for i in range(January, January+12)
266        ]
267        return [months[i:i+width] for i in range(0, len(months), width) ]
268
269    def yeardays2calendar(self, year, width=3):
270        """
271        Return the data for the specified year ready for formatting (similar to
272        yeardatescalendar()). Entries in the week lists are
273        (day number, weekday number) tuples. Day numbers outside this month are
274        zero.
275        """
276        months = [
277            self.monthdays2calendar(year, i)
278            for i in range(January, January+12)
279        ]
280        return [months[i:i+width] for i in range(0, len(months), width) ]
281
282    def yeardayscalendar(self, year, width=3):
283        """
284        Return the data for the specified year ready for formatting (similar to
285        yeardatescalendar()). Entries in the week lists are day numbers.
286        Day numbers outside this month are zero.
287        """
288        months = [
289            self.monthdayscalendar(year, i)
290            for i in range(January, January+12)
291        ]
292        return [months[i:i+width] for i in range(0, len(months), width) ]
293
294
295class TextCalendar(Calendar):
296    """
297    Subclass of Calendar that outputs a calendar as a simple plain text
298    similar to the UNIX program cal.
299    """
300
301    def prweek(self, theweek, width):
302        """
303        Print a single week (no newline).
304        """
305        print(self.formatweek(theweek, width), end='')
306
307    def formatday(self, day, weekday, width):
308        """
309        Returns a formatted day.
310        """
311        if day == 0:
312            s = ''
313        else:
314            s = '%2i' % day             # right-align single-digit days
315        return s.center(width)
316
317    def formatweek(self, theweek, width):
318        """
319        Returns a single week in a string (no newline).
320        """
321        return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
322
323    def formatweekday(self, day, width):
324        """
325        Returns a formatted week day name.
326        """
327        if width >= 9:
328            names = day_name
329        else:
330            names = day_abbr
331        return names[day][:width].center(width)
332
333    def formatweekheader(self, width):
334        """
335        Return a header for a week.
336        """
337        return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
338
339    def formatmonthname(self, theyear, themonth, width, withyear=True):
340        """
341        Return a formatted month name.
342        """
343        s = month_name[themonth]
344        if withyear:
345            s = "%s %r" % (s, theyear)
346        return s.center(width)
347
348    def prmonth(self, theyear, themonth, w=0, l=0):
349        """
350        Print a month's calendar.
351        """
352        print(self.formatmonth(theyear, themonth, w, l), end='')
353
354    def formatmonth(self, theyear, themonth, w=0, l=0):
355        """
356        Return a month's calendar string (multi-line).
357        """
358        w = max(2, w)
359        l = max(1, l)
360        s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
361        s = s.rstrip()
362        s += '\n' * l
363        s += self.formatweekheader(w).rstrip()
364        s += '\n' * l
365        for week in self.monthdays2calendar(theyear, themonth):
366            s += self.formatweek(week, w).rstrip()
367            s += '\n' * l
368        return s
369
370    def formatyear(self, theyear, w=2, l=1, c=6, m=3):
371        """
372        Returns a year's calendar as a multi-line string.
373        """
374        w = max(2, w)
375        l = max(1, l)
376        c = max(2, c)
377        colwidth = (w + 1) * 7 - 1
378        v = []
379        a = v.append
380        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
381        a('\n'*l)
382        header = self.formatweekheader(w)
383        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
384            # months in this row
385            months = range(m*i+1, min(m*(i+1)+1, 13))
386            a('\n'*l)
387            names = (self.formatmonthname(theyear, k, colwidth, False)
388                     for k in months)
389            a(formatstring(names, colwidth, c).rstrip())
390            a('\n'*l)
391            headers = (header for k in months)
392            a(formatstring(headers, colwidth, c).rstrip())
393            a('\n'*l)
394            # max number of weeks for this row
395            height = max(len(cal) for cal in row)
396            for j in range(height):
397                weeks = []
398                for cal in row:
399                    if j >= len(cal):
400                        weeks.append('')
401                    else:
402                        weeks.append(self.formatweek(cal[j], w))
403                a(formatstring(weeks, colwidth, c).rstrip())
404                a('\n' * l)
405        return ''.join(v)
406
407    def pryear(self, theyear, w=0, l=0, c=6, m=3):
408        """Print a year's calendar."""
409        print(self.formatyear(theyear, w, l, c, m), end='')
410
411
412class HTMLCalendar(Calendar):
413    """
414    This calendar returns complete HTML pages.
415    """
416
417    # CSS classes for the day <td>s
418    cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
419
420    # CSS classes for the day <th>s
421    cssclasses_weekday_head = cssclasses
422
423    # CSS class for the days before and after current month
424    cssclass_noday = "noday"
425
426    # CSS class for the month's head
427    cssclass_month_head = "month"
428
429    # CSS class for the month
430    cssclass_month = "month"
431
432    # CSS class for the year's table head
433    cssclass_year_head = "year"
434
435    # CSS class for the whole year table
436    cssclass_year = "year"
437
438    def formatday(self, day, weekday):
439        """
440        Return a day as a table cell.
441        """
442        if day == 0:
443            # day outside month
444            return '<td class="%s">&nbsp;</td>' % self.cssclass_noday
445        else:
446            return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
447
448    def formatweek(self, theweek):
449        """
450        Return a complete week as a table row.
451        """
452        s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
453        return '<tr>%s</tr>' % s
454
455    def formatweekday(self, day):
456        """
457        Return a weekday name as a table header.
458        """
459        return '<th class="%s">%s</th>' % (
460            self.cssclasses_weekday_head[day], day_abbr[day])
461
462    def formatweekheader(self):
463        """
464        Return a header for a week as a table row.
465        """
466        s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
467        return '<tr>%s</tr>' % s
468
469    def formatmonthname(self, theyear, themonth, withyear=True):
470        """
471        Return a month name as a table row.
472        """
473        if withyear:
474            s = '%s %s' % (month_name[themonth], theyear)
475        else:
476            s = '%s' % month_name[themonth]
477        return '<tr><th colspan="7" class="%s">%s</th></tr>' % (
478            self.cssclass_month_head, s)
479
480    def formatmonth(self, theyear, themonth, withyear=True):
481        """
482        Return a formatted month as a table.
483        """
484        v = []
485        a = v.append
486        a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' % (
487            self.cssclass_month))
488        a('\n')
489        a(self.formatmonthname(theyear, themonth, withyear=withyear))
490        a('\n')
491        a(self.formatweekheader())
492        a('\n')
493        for week in self.monthdays2calendar(theyear, themonth):
494            a(self.formatweek(week))
495            a('\n')
496        a('</table>')
497        a('\n')
498        return ''.join(v)
499
500    def formatyear(self, theyear, width=3):
501        """
502        Return a formatted year as a table of tables.
503        """
504        v = []
505        a = v.append
506        width = max(width, 1)
507        a('<table border="0" cellpadding="0" cellspacing="0" class="%s">' %
508          self.cssclass_year)
509        a('\n')
510        a('<tr><th colspan="%d" class="%s">%s</th></tr>' % (
511            width, self.cssclass_year_head, theyear))
512        for i in range(January, January+12, width):
513            # months in this row
514            months = range(i, min(i+width, 13))
515            a('<tr>')
516            for m in months:
517                a('<td>')
518                a(self.formatmonth(theyear, m, withyear=False))
519                a('</td>')
520            a('</tr>')
521        a('</table>')
522        return ''.join(v)
523
524    def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
525        """
526        Return a formatted year as a complete HTML page.
527        """
528        if encoding is None:
529            encoding = sys.getdefaultencoding()
530        v = []
531        a = v.append
532        a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
533        a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
534        a('<html>\n')
535        a('<head>\n')
536        a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
537        if css is not None:
538            a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
539        a('<title>Calendar for %d</title>\n' % theyear)
540        a('</head>\n')
541        a('<body>\n')
542        a(self.formatyear(theyear, width))
543        a('</body>\n')
544        a('</html>\n')
545        return ''.join(v).encode(encoding, "xmlcharrefreplace")
546
547
548class different_locale:
549    def __init__(self, locale):
550        self.locale = locale
551        self.oldlocale = None
552
553    def __enter__(self):
554        self.oldlocale = _locale.setlocale(_locale.LC_TIME, None)
555        _locale.setlocale(_locale.LC_TIME, self.locale)
556
557    def __exit__(self, *args):
558        if self.oldlocale is None:
559            return
560        _locale.setlocale(_locale.LC_TIME, self.oldlocale)
561
562
563def _get_default_locale():
564    locale = _locale.setlocale(_locale.LC_TIME, None)
565    if locale == "C":
566        with different_locale(""):
567            # The LC_TIME locale does not seem to be configured:
568            # get the user preferred locale.
569            locale = _locale.setlocale(_locale.LC_TIME, None)
570    return locale
571
572
573class LocaleTextCalendar(TextCalendar):
574    """
575    This class can be passed a locale name in the constructor and will return
576    month and weekday names in the specified locale.
577    """
578
579    def __init__(self, firstweekday=0, locale=None):
580        TextCalendar.__init__(self, firstweekday)
581        if locale is None:
582            locale = _get_default_locale()
583        self.locale = locale
584
585    def formatweekday(self, day, width):
586        with different_locale(self.locale):
587            return super().formatweekday(day, width)
588
589    def formatmonthname(self, theyear, themonth, width, withyear=True):
590        with different_locale(self.locale):
591            return super().formatmonthname(theyear, themonth, width, withyear)
592
593
594class LocaleHTMLCalendar(HTMLCalendar):
595    """
596    This class can be passed a locale name in the constructor and will return
597    month and weekday names in the specified locale.
598    """
599    def __init__(self, firstweekday=0, locale=None):
600        HTMLCalendar.__init__(self, firstweekday)
601        if locale is None:
602            locale = _get_default_locale()
603        self.locale = locale
604
605    def formatweekday(self, day):
606        with different_locale(self.locale):
607            return super().formatweekday(day)
608
609    def formatmonthname(self, theyear, themonth, withyear=True):
610        with different_locale(self.locale):
611            return super().formatmonthname(theyear, themonth, withyear)
612
613# Support for old module level interface
614c = TextCalendar()
615
616firstweekday = c.getfirstweekday
617
618def setfirstweekday(firstweekday):
619    if not MONDAY <= firstweekday <= SUNDAY:
620        raise IllegalWeekdayError(firstweekday)
621    c.firstweekday = firstweekday
622
623monthcalendar = c.monthdayscalendar
624prweek = c.prweek
625week = c.formatweek
626weekheader = c.formatweekheader
627prmonth = c.prmonth
628month = c.formatmonth
629calendar = c.formatyear
630prcal = c.pryear
631
632
633# Spacing of month columns for multi-column year calendar
634_colwidth = 7*3 - 1         # Amount printed by prweek()
635_spacing = 6                # Number of spaces between columns
636
637
638def format(cols, colwidth=_colwidth, spacing=_spacing):
639    """Prints multi-column formatting for year calendars"""
640    print(formatstring(cols, colwidth, spacing))
641
642
643def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
644    """Returns a string formatted from n strings, centered within n columns."""
645    spacing *= ' '
646    return spacing.join(c.center(colwidth) for c in cols)
647
648
649EPOCH = 1970
650_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
651
652
653def timegm(tuple):
654    """Unrelated but handy function to calculate Unix timestamp from GMT."""
655    year, month, day, hour, minute, second = tuple[:6]
656    days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
657    hours = days*24 + hour
658    minutes = hours*60 + minute
659    seconds = minutes*60 + second
660    return seconds
661
662
663def main(args):
664    import argparse
665    parser = argparse.ArgumentParser()
666    textgroup = parser.add_argument_group('text only arguments')
667    htmlgroup = parser.add_argument_group('html only arguments')
668    textgroup.add_argument(
669        "-w", "--width",
670        type=int, default=2,
671        help="width of date column (default 2)"
672    )
673    textgroup.add_argument(
674        "-l", "--lines",
675        type=int, default=1,
676        help="number of lines for each week (default 1)"
677    )
678    textgroup.add_argument(
679        "-s", "--spacing",
680        type=int, default=6,
681        help="spacing between months (default 6)"
682    )
683    textgroup.add_argument(
684        "-m", "--months",
685        type=int, default=3,
686        help="months per row (default 3)"
687    )
688    htmlgroup.add_argument(
689        "-c", "--css",
690        default="calendar.css",
691        help="CSS to use for page"
692    )
693    parser.add_argument(
694        "-L", "--locale",
695        default=None,
696        help="locale to be used from month and weekday names"
697    )
698    parser.add_argument(
699        "-e", "--encoding",
700        default=None,
701        help="encoding to use for output"
702    )
703    parser.add_argument(
704        "-t", "--type",
705        default="text",
706        choices=("text", "html"),
707        help="output type (text or html)"
708    )
709    parser.add_argument(
710        "year",
711        nargs='?', type=int,
712        help="year number (1-9999)"
713    )
714    parser.add_argument(
715        "month",
716        nargs='?', type=int,
717        help="month number (1-12, text only)"
718    )
719
720    options = parser.parse_args(args[1:])
721
722    if options.locale and not options.encoding:
723        parser.error("if --locale is specified --encoding is required")
724        sys.exit(1)
725
726    locale = options.locale, options.encoding
727
728    if options.type == "html":
729        if options.locale:
730            cal = LocaleHTMLCalendar(locale=locale)
731        else:
732            cal = HTMLCalendar()
733        encoding = options.encoding
734        if encoding is None:
735            encoding = sys.getdefaultencoding()
736        optdict = dict(encoding=encoding, css=options.css)
737        write = sys.stdout.buffer.write
738        if options.year is None:
739            write(cal.formatyearpage(datetime.date.today().year, **optdict))
740        elif options.month is None:
741            write(cal.formatyearpage(options.year, **optdict))
742        else:
743            parser.error("incorrect number of arguments")
744            sys.exit(1)
745    else:
746        if options.locale:
747            cal = LocaleTextCalendar(locale=locale)
748        else:
749            cal = TextCalendar()
750        optdict = dict(w=options.width, l=options.lines)
751        if options.month is None:
752            optdict["c"] = options.spacing
753            optdict["m"] = options.months
754        if options.year is None:
755            result = cal.formatyear(datetime.date.today().year, **optdict)
756        elif options.month is None:
757            result = cal.formatyear(options.year, **optdict)
758        else:
759            result = cal.formatmonth(options.year, options.month, **optdict)
760        write = sys.stdout.write
761        if options.encoding:
762            result = result.encode(options.encoding)
763            write = sys.stdout.buffer.write
764        write(result)
765
766
767if __name__ == "__main__":
768    main(sys.argv)
769