imip-agent

Annotated imip_text_client.py

1413:0d89c84c798a
2017-12-03 Paul Boddie Support date-datetime comparisons when determining the period collection limit. client-editing-simplification
paul@1375 1
#!/usr/bin/env python
paul@1375 2
paul@1396 3
"""
paul@1396 4
A text interface to received and new events.
paul@1396 5
paul@1396 6
Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk>
paul@1396 7
paul@1396 8
This program is free software; you can redistribute it and/or modify it under
paul@1396 9
the terms of the GNU General Public License as published by the Free Software
paul@1396 10
Foundation; either version 3 of the License, or (at your option) any later
paul@1396 11
version.
paul@1396 12
paul@1396 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@1396 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@1396 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@1396 16
details.
paul@1396 17
paul@1396 18
You should have received a copy of the GNU General Public License along with
paul@1396 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@1396 20
"""
paul@1396 21
paul@1375 22
from email import message_from_file
paul@1387 23
from imiptools import get_handlers, parse_args
paul@1375 24
from imiptools.config import settings
paul@1387 25
from imiptools.content import get_objects_from_itip, handle_calendar_data, \
paul@1387 26
                              handle_calendar_object, have_itip_part, \
paul@1387 27
                              parse_itip_part
paul@1387 28
from imiptools.data import get_address, get_main_period, get_recurrence_periods, get_value, parse_object
paul@1375 29
from imiptools.dates import get_datetime_item, get_time, to_timezone
paul@1375 30
from imiptools.editing import EditingClient, PeriodError
paul@1387 31
from imiptools.handlers import person, person_outgoing
paul@1375 32
from imiptools.mail import Messenger
paul@1375 33
from imiptools.stores import get_journal, get_store
paul@1375 34
from imiptools.utils import decode_part, message_as_string
paul@1396 35
import vRecurrence
paul@1387 36
import sys, os
paul@1375 37
paul@1375 38
# User interface functions.
paul@1375 39
paul@1375 40
echo = False
paul@1375 41
paul@1375 42
def read_input(label):
paul@1396 43
paul@1396 44
    """
paul@1396 45
    Read input, prompting using 'label', stripping leading and trailing
paul@1396 46
    whitespace, echoing the input if the global 'echo' variable is set.
paul@1396 47
    """
paul@1396 48
paul@1375 49
    s = raw_input(label).strip()
paul@1375 50
    if echo:
paul@1375 51
        print s
paul@1375 52
    return s
paul@1375 53
paul@1375 54
def input_with_default(label, default):
paul@1396 55
paul@1396 56
    """
paul@1396 57
    Read input, prompting using 'label', parameterising the label with the given
paul@1396 58
    'default' and returning the default if no input is given.
paul@1396 59
    """
paul@1396 60
paul@1375 61
    return read_input(label % default) or default
paul@1375 62
paul@1375 63
def print_title(text):
paul@1396 64
paul@1396 65
    "Print 'text' with simple, fixed-width styling as a title."
paul@1396 66
paul@1375 67
    print text
paul@1375 68
    print len(text) * "-"
paul@1375 69
paul@1396 70
def print_table(rows, separator_index=0):
paul@1396 71
paul@1396 72
    """
paul@1396 73
    Print 'rows' as a simple, fixed-width table. If 'separator_index' is set to
paul@1396 74
    a row index present in the rows, a table separator will be produced below
paul@1396 75
    that row's data. Otherwise, a separator will appear below the first row.
paul@1396 76
    """
paul@1396 77
paul@1396 78
    widths = []
paul@1396 79
    for row in rows:
paul@1396 80
        for i, col in enumerate(row):
paul@1396 81
            if i >= len(widths):
paul@1396 82
                widths.append(len(col))
paul@1396 83
            else:
paul@1396 84
                widths[i] = max(widths[i], len(col))
paul@1396 85
paul@1396 86
    for i, row in enumerate(rows):
paul@1396 87
        for col, width in zip(row, widths):
paul@1396 88
            print "%s%s" % (col, " " * (width - len(col))),
paul@1396 89
        print
paul@1396 90
        if i == separator_index:
paul@1396 91
            for width in widths:
paul@1396 92
                print "-" * width,
paul@1396 93
            print
paul@1396 94
paul@1375 95
def write(s, filename):
paul@1396 96
paul@1396 97
    "Write 's' to a file having the given 'filename'."
paul@1396 98
paul@1375 99
    f = filename and open(filename, "w") or None
paul@1375 100
    try:
paul@1375 101
        print >>(f or sys.stdout), s
paul@1375 102
    finally:
paul@1375 103
        if f:
paul@1375 104
            f.close()
paul@1375 105
paul@1375 106
# Interpret an input file containing a calendar resource.
paul@1375 107
paul@1387 108
def get_itip_from_message(filename):
paul@1375 109
paul@1387 110
    "Return iTIP details provided by 'filename'."
paul@1375 111
paul@1375 112
    f = open(filename)
paul@1375 113
    try:
paul@1375 114
        msg = message_from_file(f)
paul@1375 115
    finally:
paul@1375 116
        f.close()
paul@1375 117
paul@1387 118
    all_itip = []
paul@1375 119
paul@1375 120
    for part in msg.walk():
paul@1387 121
        if have_itip_part(part):
paul@1387 122
            all_itip.append(parse_itip_part(part))
paul@1375 123
paul@1387 124
    return all_itip
paul@1387 125
paul@1387 126
def get_itip_from_data(filename, charset):
paul@1375 127
paul@1387 128
    "Return objects provided by 'filename'."
paul@1375 129
paul@1387 130
    f = open(filename)
paul@1387 131
    try:
paul@1387 132
        itip = parse_object(f, charset, "VCALENDAR")
paul@1387 133
    finally:
paul@1387 134
        f.close()
paul@1387 135
paul@1387 136
    return [itip]
paul@1375 137
paul@1375 138
def show_objects(objects, user, store):
paul@1375 139
paul@1375 140
    """
paul@1375 141
    Show details of 'objects', accessed by the given 'user' in the given
paul@1375 142
    'store'.
paul@1375 143
    """
paul@1375 144
paul@1387 145
    print
paul@1387 146
    print_title("Objects")
paul@1387 147
    print
paul@1387 148
paul@1375 149
    for index, obj in enumerate(objects):
paul@1375 150
        recurrenceid = obj.get_recurrenceid()
paul@1375 151
        recurrence_label = recurrenceid and " %s" % recurrenceid or ""
paul@1375 152
        print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label)
paul@1375 153
paul@1387 154
def show_requests(user, store):
paul@1387 155
paul@1387 156
    "Show requests available to the given 'user' in the given 'store'."
paul@1387 157
paul@1387 158
    requests = store.get_requests(user)
paul@1387 159
paul@1387 160
    print
paul@1387 161
    print_title("Requests")
paul@1387 162
    print
paul@1387 163
paul@1387 164
    if not requests:
paul@1387 165
        print "No requests are pending."
paul@1387 166
        return
paul@1387 167
paul@1387 168
    for index, (uid, recurrenceid) in enumerate(requests):
paul@1387 169
        obj = store.get_event(user, uid, recurrenceid)
paul@1387 170
        recurrence_label = recurrenceid and " %s" % recurrenceid or ""
paul@1387 171
        print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label)
paul@1387 172
paul@1375 173
def show_attendee(attendee_item, index):
paul@1375 174
paul@1375 175
    "Show the 'attendee_item' (value and attributes) at 'index'."
paul@1375 176
paul@1375 177
    attendee, attr = attendee_item
paul@1375 178
    partstat = attr.get("PARTSTAT")
paul@1375 179
    print "(%d) %s%s" % (index, attendee, partstat and " (%s)" % partstat or "")
paul@1375 180
paul@1375 181
def show_attendees_raw(attendee_map):
paul@1375 182
paul@1375 183
    "Show the 'attendee_map' in a simple raw form."
paul@1375 184
paul@1375 185
    for attendee, attr in attendee_map.items():
paul@1375 186
        print attendee
paul@1375 187
paul@1375 188
def show_periods(periods, errors=None):
paul@1375 189
paul@1375 190
    "Show 'periods' with any indicated 'errors'."
paul@1375 191
paul@1375 192
    main = get_main_period(periods)
paul@1375 193
    if main:
paul@1375 194
        show_period(main, 0, errors)
paul@1375 195
paul@1375 196
    recurrences = get_recurrence_periods(periods)
paul@1375 197
    if recurrences:
paul@1375 198
        print
paul@1375 199
        print_title("Recurrences")
paul@1375 200
        for index, p in enumerate(recurrences):
paul@1375 201
            show_period(p, index + 1, errors)
paul@1375 202
paul@1375 203
def show_period(p, index, errors=None):
paul@1375 204
paul@1375 205
    "Show period 'p' at 'index' with any indicated 'errors'."
paul@1375 206
paul@1375 207
    errors = errors and errors.get(index)
paul@1375 208
    if p.replacement:
paul@1375 209
        if p.cancelled:
paul@1375 210
            label = "Cancelled"
paul@1375 211
        else:
paul@1375 212
            label = "Replaced"
paul@1375 213
    else:
paul@1375 214
        if p.new_replacement:
paul@1375 215
            label = "To replace"
paul@1375 216
        elif p.recurrenceid:
paul@1375 217
            label = "Retained"
paul@1375 218
        else:
paul@1375 219
            label = "New"
paul@1375 220
paul@1375 221
    error_label = errors and " (errors: %s)" % ", ".join(errors) or ""
paul@1375 222
    print "(%d) %s%s:" % (index, label, error_label), p.get_start(), p.get_end(), p.origin
paul@1375 223
paul@1375 224
def show_periods_raw(periods):
paul@1375 225
paul@1375 226
    "Show 'periods' in a simple raw form."
paul@1375 227
paul@1375 228
    periods = periods[:]
paul@1375 229
    periods.sort()
paul@1375 230
    map(show_period_raw, periods)
paul@1375 231
paul@1375 232
def show_period_raw(p):
paul@1375 233
paul@1375 234
    "Show period 'p' in a simple raw form."
paul@1375 235
paul@1375 236
    print p.get_start(), p.get_end(), p.origin
paul@1375 237
paul@1396 238
def show_rule(rrule):
paul@1396 239
paul@1396 240
    "Show recurrence rule specification 'rrule'."
paul@1396 241
paul@1396 242
    print
paul@1396 243
    print "Recurrence rule:"
paul@1396 244
paul@1396 245
    count = None
paul@1396 246
    freq_interval = []
paul@1396 247
    selections = []
paul@1396 248
paul@1396 249
    # Collect limit, selection and frequency details.
paul@1396 250
paul@1396 251
    for selector in vRecurrence.order_qualifiers(vRecurrence.get_qualifiers(rrule)):
paul@1396 252
paul@1396 253
        # COUNT
paul@1396 254
paul@1396 255
        if isinstance(selector, vRecurrence.LimitSelector):
paul@1396 256
            count = selector.args["values"][0]
paul@1396 257
paul@1396 258
        # BYSETPOS
paul@1396 259
paul@1396 260
        elif isinstance(selector, vRecurrence.PositionSelector):
paul@1396 261
            for value in selector.args["values"]:
paul@1396 262
                selections.append(("-", get_frequency(selector.level), str(value)))
paul@1396 263
paul@1396 264
        # BY...
paul@1396 265
paul@1396 266
        elif isinstance(selector, vRecurrence.Enum):
paul@1396 267
            for value in selector.args["values"]:
paul@1396 268
                selections.append(("-", get_frequency(selector.level), str(value)))
paul@1396 269
paul@1396 270
        # BYWEEKDAY
paul@1396 271
paul@1396 272
        elif isinstance(selector, vRecurrence.WeekDayFilter):
paul@1396 273
            for value, index in selector.args["values"]:
paul@1396 274
                selections.append((index >= 0 and "Start" or "End", get_weekday(value), str(index)))
paul@1396 275
paul@1396 276
        # YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY
paul@1396 277
paul@1396 278
        elif isinstance(selector, vRecurrence.Pattern):
paul@1396 279
            freq_interval.append((get_frequency(selector.level), str(selector.args.get("interval", 1))))
paul@1396 280
paul@1396 281
    # Show the details.
paul@1396 282
paul@1396 283
    if freq_interval:
paul@1396 284
        print
paul@1396 285
        print_table([("Frequency", "Interval")] + freq_interval)
paul@1396 286
paul@1396 287
    if selections:
paul@1396 288
        print
paul@1396 289
        print_table([("From...", "Selecting", "Instance (1, 2, ...)")] + selections)
paul@1396 290
paul@1396 291
    if count:
paul@1396 292
        print
paul@1396 293
        print "At most", count, "occurrences."
paul@1396 294
paul@1396 295
def get_frequency(level):
paul@1396 296
    levels = ["Year", "Month", "Week", None, None, "Day", "Hour", "Minute", "Second"]
paul@1396 297
    return levels[level]
paul@1396 298
paul@1396 299
def get_weekday(weekday):
paul@1396 300
    weekdays = [None, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
paul@1396 301
    return weekdays[weekday]
paul@1396 302
paul@1375 303
def show_attendee_changes(new, modified, unmodified, removed):
paul@1375 304
paul@1375 305
    "Show 'new', 'modified', 'unmodified' and 'removed' periods."
paul@1375 306
paul@1375 307
    print
paul@1375 308
    print_title("Changes to attendees")
paul@1375 309
    print
paul@1375 310
    print "New:"
paul@1375 311
    show_attendees_raw(new)
paul@1375 312
    print
paul@1375 313
    print "Modified:"
paul@1375 314
    show_attendees_raw(modified)
paul@1375 315
    print
paul@1375 316
    print "Unmodified:"
paul@1375 317
    show_attendees_raw(unmodified)
paul@1375 318
    print
paul@1375 319
    print "Removed:"
paul@1375 320
    show_attendees_raw(removed)
paul@1375 321
paul@1375 322
def show_period_classification(new, replaced, retained, cancelled, obsolete):
paul@1375 323
paul@1375 324
    "Show 'new', 'replaced', 'retained', 'cancelled' and 'obsolete' periods."
paul@1375 325
paul@1375 326
    print
paul@1375 327
    print_title("Period classification")
paul@1375 328
    print
paul@1375 329
    print "New:"
paul@1375 330
    show_periods_raw(new)
paul@1375 331
    print
paul@1375 332
    print "Replaced:"
paul@1375 333
    show_periods_raw(replaced)
paul@1375 334
    print
paul@1375 335
    print "Retained:"
paul@1375 336
    show_periods_raw(retained)
paul@1375 337
    print
paul@1375 338
    print "Cancelled:"
paul@1375 339
    show_periods_raw(cancelled)
paul@1375 340
    print
paul@1375 341
    print "Obsolete:"
paul@1375 342
    show_periods_raw(obsolete)
paul@1375 343
paul@1375 344
def show_changes(modified, unmodified, removed):
paul@1375 345
paul@1375 346
    "Show 'modified', 'unmodified' and 'removed' periods."
paul@1375 347
paul@1375 348
    print
paul@1375 349
    print_title("Changes to periods")
paul@1375 350
    print
paul@1375 351
    print "Modified:"
paul@1375 352
    show_periods_raw(modified)
paul@1375 353
    print
paul@1375 354
    print "Unmodified:"
paul@1375 355
    show_periods_raw(unmodified)
paul@1375 356
    print
paul@1375 357
    print "Removed:"
paul@1375 358
    show_periods_raw(removed)
paul@1375 359
paul@1375 360
def show_attendee_operations(to_invite, to_cancel, to_modify):
paul@1375 361
paul@1375 362
    "Show attendees 'to_invite', 'to_cancel' and 'to_modify'."
paul@1375 363
paul@1375 364
    print
paul@1375 365
    print_title("Attendee update operations")
paul@1375 366
    print
paul@1375 367
    print "To invite:"
paul@1375 368
    show_attendees_raw(to_invite)
paul@1375 369
    print
paul@1375 370
    print "To cancel:"
paul@1375 371
    show_attendees_raw(to_cancel)
paul@1375 372
    print
paul@1375 373
    print "To modify:"
paul@1375 374
    show_attendees_raw(to_modify)
paul@1375 375
paul@1375 376
def show_period_operations(to_unschedule, to_reschedule, to_add, to_exclude, to_set,
paul@1375 377
                           all_unscheduled, all_rescheduled):
paul@1375 378
paul@1375 379
    """
paul@1375 380
    Show operations for periods 'to_unschedule', 'to_reschedule', 'to_add',
paul@1375 381
    'to_exclude' and 'to_set' (for updating other calendar participants), and
paul@1375 382
    for periods 'all_unscheduled' and 'all_rescheduled' (for publishing event
paul@1375 383
    state).
paul@1375 384
    """
paul@1375 385
paul@1375 386
    print
paul@1375 387
    print_title("Period update and publishing operations")
paul@1375 388
    print
paul@1375 389
    print "Unschedule:"
paul@1375 390
    show_periods_raw(to_unschedule)
paul@1375 391
    print
paul@1375 392
    print "Reschedule:"
paul@1375 393
    show_periods_raw(to_reschedule)
paul@1375 394
    print
paul@1375 395
    print "Added:"
paul@1375 396
    show_periods_raw(to_add)
paul@1375 397
    print
paul@1375 398
    print "Excluded:"
paul@1375 399
    show_periods_raw(to_exclude)
paul@1375 400
    print
paul@1375 401
    print "Set in object:"
paul@1375 402
    show_periods_raw(to_set)
paul@1375 403
    print
paul@1375 404
    print "All unscheduled:"
paul@1375 405
    show_periods_raw(all_unscheduled)
paul@1375 406
    print
paul@1375 407
    print "All rescheduled:"
paul@1375 408
    show_periods_raw(all_rescheduled)
paul@1375 409
paul@1375 410
class TextClient(EditingClient):
paul@1375 411
paul@1375 412
    "Simple client with textual output."
paul@1375 413
paul@1375 414
    def new_object(self):
paul@1375 415
paul@1375 416
        "Create a new object with the current time."
paul@1375 417
paul@1375 418
        utcnow = get_time()
paul@1375 419
        now = to_timezone(utcnow, self.get_tzid())
paul@1375 420
        obj = EditingClient.new_object(self, "VEVENT")
paul@1375 421
        obj.set_value("SUMMARY", "New event")
paul@1375 422
        obj["DTSTART"] = [get_datetime_item(now)]
paul@1375 423
        obj["DTEND"] = [get_datetime_item(now)]
paul@1375 424
        return obj
paul@1375 425
paul@1375 426
    # Editing methods involving interaction.
paul@1375 427
paul@1375 428
    def edit_attendee(self, index):
paul@1375 429
paul@1375 430
        "Edit the attendee at 'index'."
paul@1375 431
paul@1375 432
        t = self.can_edit_attendee(index)
paul@1375 433
        if t:
paul@1375 434
            attendees = self.state.get("attendees")
paul@1375 435
            attendee, attr = t
paul@1375 436
            del attendees[attendee]
paul@1375 437
            attendee = input_with_default("Attendee (%s)? ", attendee)
paul@1375 438
            attendees[attendee] = attr
paul@1375 439
paul@1375 440
    def edit_period(self, index, args=None):
paul@1375 441
        period = self.can_edit_period(index)
paul@1375 442
        if period:
paul@1375 443
            edit_period(period, args)
paul@1375 444
            period.cancelled = False
paul@1396 445
            period.origin = "DTSTART-RECUR"
paul@1375 446
paul@1375 447
            # Sort the periods after this change.
paul@1375 448
paul@1375 449
            periods = self.state.get("periods")
paul@1375 450
            periods.sort()
paul@1375 451
paul@1375 452
    def edit_summary(self, summary=None):
paul@1375 453
        if self.can_edit_properties():
paul@1375 454
            if not summary:
paul@1375 455
                summary = input_with_default("Summary (%s)? ", self.state.get("summary"))
paul@1375 456
            self.state.set("summary", summary)
paul@1375 457
paul@1375 458
    def finish(self):
paul@1375 459
        try:
paul@1375 460
            EditingClient.finish(self)
paul@1375 461
        except PeriodError:
paul@1375 462
            print "Errors exist in the periods."
paul@1375 463
            return
paul@1375 464
paul@1375 465
    # Diagnostic methods.
paul@1375 466
paul@1375 467
    def show_period_classification(self):
paul@1375 468
        try:
paul@1375 469
            new, replaced, retained, cancelled, obsolete = self.classify_periods()
paul@1375 470
            show_period_classification(new, replaced, retained, cancelled, obsolete)
paul@1375 471
        except PeriodError:
paul@1375 472
            print
paul@1375 473
            print "Errors exist in the periods."
paul@1375 474
paul@1375 475
    def show_changes(self):
paul@1375 476
        try:
paul@1375 477
            modified, unmodified, removed = self.classify_period_changes()
paul@1375 478
            show_changes(modified, unmodified, removed)
paul@1375 479
        except PeriodError:
paul@1375 480
            print "Errors exist in the periods."
paul@1375 481
paul@1375 482
        is_changed = self.properties_changed()
paul@1375 483
        if is_changed:
paul@1375 484
            print
paul@1375 485
            print "Properties changed:", ", ".join(is_changed)
paul@1375 486
        new, modified, unmodified, removed = self.classify_attendee_changes()
paul@1375 487
        show_attendee_changes(new, modified, unmodified, removed)
paul@1375 488
paul@1375 489
    def show_operations(self):
paul@1375 490
        is_changed = self.properties_changed()
paul@1375 491
paul@1375 492
        try:
paul@1375 493
            to_unschedule, to_reschedule, to_add, to_exclude, to_set, \
paul@1375 494
                all_unscheduled, all_rescheduled = self.classify_period_operations()
paul@1375 495
            show_period_operations(to_unschedule, to_reschedule, to_add,
paul@1375 496
                                   to_exclude, to_set,
paul@1375 497
                                   all_unscheduled, all_rescheduled)
paul@1375 498
        except PeriodError:
paul@1375 499
            print "Errors exist in the periods."
paul@1375 500
paul@1375 501
        to_invite, to_cancel, to_modify = self.classify_attendee_operations()
paul@1375 502
        show_attendee_operations(to_invite, to_cancel, to_modify)
paul@1375 503
paul@1375 504
    # Output methods.
paul@1375 505
paul@1375 506
    def show_message(self, message, plain=False, filename=None):
paul@1375 507
        if plain:
paul@1375 508
            decode_part(message)
paul@1375 509
        write(message_as_string(message), filename)
paul@1375 510
paul@1375 511
    def show_cancel_message(self, plain=False, filename=None):
paul@1375 512
paul@1375 513
        "Show the cancel message for uninvited attendees."
paul@1375 514
paul@1375 515
        message = self.prepare_cancel_message()
paul@1375 516
        if message:
paul@1375 517
            self.show_message(message, plain, filename)
paul@1375 518
paul@1375 519
    def show_publish_message(self, plain=False, filename=None):
paul@1375 520
paul@1375 521
        "Show the publishing message for the updated event."
paul@1375 522
paul@1375 523
        message = self.prepare_publish_message()
paul@1375 524
        self.show_message(message, plain, filename)
paul@1375 525
paul@1375 526
    def show_update_message(self, plain=False, filename=None):
paul@1375 527
paul@1375 528
        "Show the update message for the updated event."
paul@1375 529
paul@1375 530
        message = self.prepare_update_message()
paul@1375 531
        if message:
paul@1375 532
            self.show_message(message, plain, filename)
paul@1375 533
paul@1375 534
    # General display methods.
paul@1375 535
paul@1375 536
    def show_object(self):
paul@1375 537
        print
paul@1387 538
        print_title("Object details")
paul@1387 539
        print
paul@1375 540
        print "Summary:", self.state.get("summary")
paul@1375 541
        print
paul@1375 542
        print "Organiser:", self.state.get("organiser")
paul@1375 543
        self.show_attendees()
paul@1375 544
        self.show_periods()
paul@1375 545
        self.show_suggested_attendees()
paul@1375 546
        self.show_suggested_periods()
paul@1375 547
        self.show_conflicting_periods()
paul@1375 548
paul@1375 549
    def show_attendees(self):
paul@1375 550
        print
paul@1375 551
        print_title("Attendees")
paul@1375 552
        attendees = self.state.get("attendees")
paul@1375 553
        for index, attendee_item in enumerate(attendees.items()):
paul@1375 554
            show_attendee(attendee_item, index)
paul@1375 555
paul@1375 556
    def show_periods(self):
paul@1375 557
        print
paul@1375 558
        print_title("Periods")
paul@1396 559
        rrule = self.obj.get_value("RRULE")
paul@1375 560
        show_periods(self.state.get("periods"), self.state.get("period_errors"))
paul@1396 561
        if rrule:
paul@1396 562
            show_rule(rrule)
paul@1375 563
paul@1375 564
    def show_suggested_attendees(self):
paul@1375 565
        current_attendee = None
paul@1375 566
        for index, (attendee, suggested_item) in enumerate(self.state.get("suggested_attendees")):
paul@1375 567
            if attendee != current_attendee:
paul@1375 568
                print
paul@1375 569
                print_title("Attendees suggested by %s" % attendee)
paul@1375 570
                current_attendee = attendee
paul@1375 571
            show_attendee(suggested_item, index)
paul@1375 572
paul@1375 573
    def show_suggested_periods(self):
paul@1375 574
        periods = self.state.get("suggested_periods")
paul@1375 575
        current_attendee = None
paul@1375 576
        index = 0
paul@1375 577
        for attendee, period, operation in periods:
paul@1375 578
            if attendee != current_attendee:
paul@1375 579
                print
paul@1375 580
                print_title("Periods suggested by %s" % attendee)
paul@1375 581
                current_attendee = attendee
paul@1375 582
            show_period(period, index)
paul@1375 583
            print "  %s" % (operation == "add" and "Add this period" or "Remove this period")
paul@1375 584
            index += 1
paul@1375 585
paul@1375 586
    def show_conflicting_periods(self):
paul@1375 587
        conflicts = self.get_conflicting_periods()
paul@1375 588
        if not conflicts:
paul@1375 589
            return
paul@1375 590
        print
paul@1375 591
        print_title("Conflicting periods")
paul@1375 592
paul@1375 593
        conflicts = list(conflicts)
paul@1375 594
        conflicts.sort()
paul@1375 595
paul@1375 596
        for p in conflicts:
paul@1375 597
            print p.summary, p.uid, p.get_start(), p.get_end()
paul@1375 598
paul@1375 599
# Interaction functions.
paul@1375 600
paul@1375 601
def expand_arg(args):
paul@1375 602
    if args[0] and args[0][1:].isdigit():
paul@1375 603
        args[:1] = [args[0][0], args[0][1:]]
paul@1375 604
paul@1375 605
def get_filename_arg(cmd):
paul@1375 606
    return (cmd.split()[1:] or [None])[0]
paul@1375 607
paul@1375 608
def next_arg(args):
paul@1375 609
    if args:
paul@1375 610
        arg = args[0]
paul@1375 611
        del args[0]
paul@1375 612
        return arg
paul@1375 613
    return None
paul@1375 614
paul@1375 615
def edit_period(period, args=None):
paul@1375 616
paul@1375 617
    "Edit the given 'period'."
paul@1375 618
paul@1375 619
    print "Editing start (%s)" % period.get_start()
paul@1375 620
    edit_date(period.start, args)
paul@1375 621
    print "Editing end (%s)" % period.get_end()
paul@1375 622
    edit_date(period.end, args)
paul@1375 623
paul@1375 624
def edit_date(date, args=None):
paul@1375 625
paul@1375 626
    "Edit the given 'date' object attributes."
paul@1375 627
paul@1375 628
    date.date = next_arg(args) or input_with_default("Date (%s)? ", date.date)
paul@1375 629
    date.hour = next_arg(args) or input_with_default("Hour (%s)? ", date.hour)
paul@1375 630
    date.minute = next_arg(args) or input_with_default("Minute (%s)? ", date.minute)
paul@1375 631
    date.second = next_arg(args) or input_with_default("Second (%s)? ", date.second)
paul@1375 632
    date.tzid = next_arg(args) or input_with_default("Time zone (%s)? ", date.tzid)
paul@1375 633
    date.reset()
paul@1375 634
paul@1375 635
def select_object(cl, objects):
paul@1375 636
    print
paul@1387 637
paul@1387 638
    if objects:
paul@1387 639
        label = "Select object number or (n)ew object or (q)uit> "
paul@1387 640
    else:
paul@1387 641
        label = "Select (n)ew object or (q)uit> "
paul@1387 642
paul@1375 643
    while True:
paul@1375 644
        try:
paul@1387 645
            cmd = read_input(label)
paul@1375 646
        except EOFError:
paul@1375 647
            return None
paul@1375 648
paul@1375 649
        if cmd.isdigit():
paul@1375 650
            index = int(cmd)
paul@1375 651
            if 0 <= index < len(objects):
paul@1375 652
                obj = objects[index]
paul@1375 653
                return cl.load_object(obj.get_uid(), obj.get_recurrenceid())
paul@1387 654
paul@1375 655
        elif cmd in ("n", "new"):
paul@1375 656
            return cl.new_object()
paul@1375 657
        elif cmd in ("q", "quit", "exit"):
paul@1375 658
            return None
paul@1375 659
paul@1387 660
def show_commands():
paul@1375 661
    print
paul@1375 662
    print_title("Editing commands")
paul@1375 663
    print
paul@1375 664
    print """\
paul@1375 665
a [ <uri> ]
paul@1375 666
attendee [ <uri> ]
paul@1375 667
   Add attendee
paul@1375 668
paul@1375 669
A, attend, attendance
paul@1375 670
   Change attendance/participation
paul@1375 671
paul@1375 672
a<digit>
paul@1375 673
attendee <digit>
paul@1375 674
   Select attendee from list
paul@1375 675
paul@1375 676
as<digit>
paul@1375 677
   Add suggested attendee from list
paul@1375 678
paul@1375 679
f, finish
paul@1375 680
   Finish editing, confirming changes, proceeding to messaging
paul@1375 681
paul@1375 682
h, help, ?
paul@1375 683
   Show this help message
paul@1375 684
paul@1375 685
l, list, show
paul@1375 686
   List/show all event details
paul@1375 687
paul@1375 688
p, period
paul@1375 689
   Add new period
paul@1375 690
paul@1375 691
p<digit>
paul@1375 692
period <digit>
paul@1375 693
   Select period from list
paul@1375 694
paul@1375 695
ps<digit>
paul@1375 696
   Add or remove suggested period from list
paul@1375 697
paul@1375 698
q, quit, exit
paul@1375 699
   Exit/quit this program
paul@1375 700
paul@1375 701
r, reload, reset, restart
paul@1375 702
   Reset event periods (return to editing mode, if already finished)
paul@1375 703
paul@1396 704
rrule <rule specification>
paul@1396 705
   Set a recurrence rule in the event, applying to the main period
paul@1396 706
paul@1375 707
s, summary
paul@1375 708
   Set event summary
paul@1375 709
"""
paul@1375 710
paul@1387 711
    print_title("Messaging commands")
paul@1387 712
    print
paul@1387 713
    print """\
paul@1387 714
S, send
paul@1387 715
   Send messages to recipients and to self, if appropriate
paul@1387 716
"""
paul@1387 717
paul@1375 718
    print_title("Diagnostic commands")
paul@1375 719
    print
paul@1375 720
    print """\
paul@1375 721
c, class, classification
paul@1375 722
   Show period classification
paul@1375 723
paul@1375 724
C, changes
paul@1375 725
   Show changes made by editing
paul@1375 726
paul@1375 727
o, ops, operations
paul@1375 728
   Show update operations
paul@1375 729
paul@1375 730
RECURRENCE-ID [ <filename> ]
paul@1375 731
   Show event recurrence identifier, writing to <filename> if specified
paul@1375 732
paul@1375 733
UID [ <filename> ]
paul@1375 734
   Show event unique identifier, writing to <filename> if specified
paul@1375 735
"""
paul@1375 736
paul@1387 737
    print_title("Message inspection commands")
paul@1375 738
    print
paul@1375 739
    print """\
paul@1375 740
P [ <filename> ]
paul@1375 741
publish [ <filename> ]
paul@1375 742
   Show publishing message, writing to <filename> if specified
paul@1375 743
paul@1375 744
R [ <filename> ]
paul@1375 745
remove [ <filename> ]
paul@1375 746
cancel [ <filename> ]
paul@1375 747
   Show cancellation message sent to uninvited/removed recipients, writing to
paul@1375 748
   <filename> if specified
paul@1375 749
paul@1375 750
U [ <filename> ]
paul@1375 751
update [ <filename> ]
paul@1375 752
   Show update message, writing to <filename> if specified
paul@1375 753
"""
paul@1375 754
paul@1387 755
def edit_object(cl, obj, handle_outgoing=False):
paul@1375 756
    cl.show_object()
paul@1375 757
    print
paul@1375 758
paul@1375 759
    try:
paul@1375 760
        while True:
paul@1375 761
            role = cl.is_organiser() and "Organiser" or "Attendee"
paul@1375 762
            status = cl.state.get("finished") and " (editing complete)" or ""
paul@1375 763
paul@1375 764
            cmd = read_input("%s%s> " % (role, status))
paul@1375 765
paul@1375 766
            args = cmd.split()
paul@1375 767
paul@1375 768
            if not args or not args[0]:
paul@1375 769
                continue
paul@1375 770
paul@1375 771
            # Check the status of the periods.
paul@1375 772
paul@1375 773
            if cmd in ("c", "class", "classification"):
paul@1375 774
                cl.show_period_classification()
paul@1375 775
                print
paul@1375 776
paul@1375 777
            elif cmd in ("C", "changes"):
paul@1375 778
                cl.show_changes()
paul@1375 779
                print
paul@1375 780
paul@1375 781
            # Finish editing.
paul@1375 782
paul@1375 783
            elif cmd in ("f", "finish"):
paul@1375 784
                cl.finish()
paul@1375 785
paul@1375 786
            # Help.
paul@1375 787
paul@1375 788
            elif cmd in ("h", "?", "help"):
paul@1387 789
                show_commands()
paul@1375 790
paul@1375 791
            # Show object details.
paul@1375 792
paul@1375 793
            elif cmd in ("l", "list", "show"):
paul@1375 794
                cl.show_object()
paul@1375 795
                print
paul@1375 796
paul@1375 797
            # Show the operations.
paul@1375 798
paul@1375 799
            elif cmd in ("o", "ops", "operations"):
paul@1375 800
                cl.show_operations()
paul@1375 801
                print
paul@1375 802
paul@1375 803
            # Quit or exit.
paul@1375 804
paul@1375 805
            elif cmd in ("q", "quit", "exit"):
paul@1375 806
                break
paul@1375 807
paul@1375 808
            # Restart editing.
paul@1375 809
paul@1375 810
            elif cmd in ("r", "reload", "reset", "restart"):
paul@1375 811
                obj = cl.load_object(obj.get_uid(), obj.get_recurrenceid())
paul@1375 812
                if not obj:
paul@1375 813
                    obj = cl.new_object()
paul@1375 814
                cl.reset()
paul@1375 815
                cl.show_object()
paul@1375 816
                print
paul@1375 817
paul@1375 818
            # Show UID details.
paul@1375 819
paul@1375 820
            elif args[0] == "UID":
paul@1375 821
                filename = get_filename_arg(cmd)
paul@1375 822
                write(obj.get_uid(), filename)
paul@1375 823
paul@1375 824
            elif args[0] == "RECURRENCE-ID":
paul@1375 825
                filename = get_filename_arg(cmd)
paul@1375 826
                write(obj.get_recurrenceid() or "", filename)
paul@1375 827
paul@1375 828
            # Post-editing operations.
paul@1375 829
paul@1375 830
            elif cl.state.get("finished"):
paul@1375 831
paul@1375 832
                # Show messages.
paul@1375 833
paul@1375 834
                if args[0] in ("P", "publish"):
paul@1375 835
                    filename = get_filename_arg(cmd)
paul@1375 836
                    cl.show_publish_message(plain=not filename, filename=filename)
paul@1375 837
paul@1375 838
                elif args[0] in ("R", "remove", "cancel"):
paul@1375 839
                    filename = get_filename_arg(cmd)
paul@1375 840
                    cl.show_cancel_message(plain=not filename, filename=filename)
paul@1375 841
paul@1375 842
                elif args[0] in ("U", "update"):
paul@1375 843
                    filename = get_filename_arg(cmd)
paul@1375 844
                    cl.show_update_message(plain=not filename, filename=filename)
paul@1375 845
paul@1387 846
                # Definitive finishing action.
paul@1387 847
paul@1387 848
                elif args[0] in ("S", "send"):
paul@1387 849
paul@1387 850
                    # Send update and cancellation messages.
paul@1387 851
paul@1387 852
                    did_send = False
paul@1387 853
paul@1387 854
                    message = cl.prepare_update_message()
paul@1387 855
                    if message:
paul@1387 856
                        cl.send_message(message, cl.get_recipients())
paul@1387 857
                        did_send = True
paul@1387 858
paul@1387 859
                    to_cancel = cl.state.get("attendees_to_cancel")
paul@1387 860
                    if to_cancel:
paul@1387 861
                        message = cl.prepare_cancel_message()
paul@1387 862
                        if message:
paul@1387 863
                            cl.send_message(message, to_cancel)
paul@1387 864
                            did_send = True
paul@1387 865
paul@1387 866
                    # Process the object using the person outgoing handler.
paul@1387 867
paul@1387 868
                    if handle_outgoing:
paul@1387 869
paul@1387 870
                        # Handle the parent object plus any rescheduled periods.
paul@1387 871
paul@1387 872
                        unscheduled_objects, rescheduled_objects, added_objects = \
paul@1387 873
                            cl.get_publish_objects()
paul@1387 874
paul@1387 875
                        handlers = get_handlers(cl, person_outgoing.handlers,
paul@1387 876
                                                [get_address(cl.user)])
paul@1387 877
paul@1387 878
                        handle_calendar_object(cl.obj, handlers, "PUBLISH")
paul@1387 879
                        for o in unscheduled_objects:
paul@1387 880
                            handle_calendar_object(o, handlers, "CANCEL")
paul@1387 881
                        for o in rescheduled_objects:
paul@1387 882
                            handle_calendar_object(o, handlers, "PUBLISH")
paul@1387 883
                        for o in added_objects:
paul@1387 884
                            handle_calendar_object(o, handlers, "ADD")
paul@1387 885
paul@1387 886
                    # Otherwise, send a message to self with the event details.
paul@1387 887
paul@1387 888
                    else:
paul@1387 889
                        message = cl.prepare_publish_message()
paul@1387 890
                        if message:
paul@1387 891
                            cl.send_message_to_self(message)
paul@1387 892
                            did_send = True
paul@1387 893
paul@1387 894
                    # Exit if sending occurred.
paul@1387 895
paul@1387 896
                    if did_send:
paul@1387 897
                        break
paul@1387 898
                    else:
paul@1387 899
                        print "No messages sent. Try making edits or exit manually."
paul@1387 900
paul@1375 901
            # Editing operations.
paul@1375 902
paul@1375 903
            elif not cl.state.get("finished"):
paul@1375 904
paul@1375 905
                # Expand short-form arguments.
paul@1375 906
paul@1375 907
                expand_arg(args)
paul@1375 908
paul@1375 909
                # Add or edit attendee.
paul@1375 910
paul@1375 911
                if args[0] in ("a", "attendee"):
paul@1375 912
paul@1375 913
                    args = args[1:]
paul@1375 914
                    value = next_arg(args)
paul@1375 915
paul@1375 916
                    if value and value.isdigit():
paul@1375 917
                        index = int(value)
paul@1375 918
                    else:
paul@1375 919
                        try:
paul@1375 920
                            index = cl.find_attendee(value)
paul@1375 921
                        except ValueError:
paul@1375 922
                            index = None
paul@1375 923
paul@1375 924
                    # Add an attendee.
paul@1375 925
paul@1375 926
                    if index is None:
paul@1375 927
                        cl.add_attendee(value)
paul@1375 928
                        if not value:
paul@1375 929
                            cl.edit_attendee(-1)
paul@1375 930
paul@1375 931
                    # Edit attendee (using index).
paul@1375 932
paul@1375 933
                    else:
paul@1375 934
                        attendee_item = cl.can_remove_attendee(index)
paul@1375 935
                        if attendee_item:
paul@1375 936
                            while True:
paul@1375 937
                                show_attendee(attendee_item, index)
paul@1375 938
paul@1375 939
                                # Obtain a command from any arguments.
paul@1375 940
paul@1375 941
                                cmd = next_arg(args)
paul@1375 942
                                if not cmd:
paul@1375 943
                                    cmd = read_input("  (e)dit, (r)emove (or return)> ")
paul@1375 944
                                if cmd in ("e", "edit"):
paul@1375 945
                                    cl.edit_attendee(index)
paul@1375 946
                                elif cmd in ("r", "remove"):
paul@1375 947
                                    cl.remove_attendees([index])
paul@1375 948
                                elif not cmd:
paul@1375 949
                                    pass
paul@1375 950
                                else:
paul@1375 951
                                    continue
paul@1375 952
                                break
paul@1375 953
paul@1375 954
                    cl.show_attendees()
paul@1375 955
                    print
paul@1375 956
paul@1375 957
                # Add suggested attendee (using index).
paul@1375 958
paul@1375 959
                elif args[0] in ("as", "attendee-suggested", "suggested-attendee"):
paul@1375 960
                    try:
paul@1375 961
                        index = int(args[1])
paul@1375 962
                        cl.add_suggested_attendee(index)
paul@1375 963
                    except ValueError:
paul@1375 964
                        pass
paul@1375 965
                    cl.show_attendees()
paul@1375 966
                    print
paul@1375 967
paul@1375 968
                # Edit attendance.
paul@1375 969
paul@1375 970
                elif args[0] in ("A", "attend", "attendance"):
paul@1375 971
paul@1375 972
                    args = args[1:]
paul@1375 973
paul@1375 974
                    if not cl.is_attendee() and cl.is_organiser():
paul@1375 975
                        cl.add_attendee(cl.user)
paul@1375 976
paul@1375 977
                    # NOTE: Support delegation.
paul@1375 978
paul@1375 979
                    if cl.can_edit_attendance():
paul@1375 980
                        while True:
paul@1375 981
paul@1375 982
                            # Obtain a command from any arguments.
paul@1375 983
paul@1375 984
                            cmd = next_arg(args)
paul@1375 985
                            if not cmd:
paul@1375 986
                                cmd = read_input("  (a)ccept, (d)ecline, (t)entative (or return)> ")
paul@1375 987
                            if cmd in ("a", "accept", "accepted", "attend"):
paul@1375 988
                                cl.edit_attendance("ACCEPTED")
paul@1375 989
                            elif cmd in ("d", "decline", "declined"):
paul@1375 990
                                cl.edit_attendance("DECLINED")
paul@1375 991
                            elif cmd in ("t", "tentative"):
paul@1375 992
                                cl.edit_attendance("TENTATIVE")
paul@1375 993
                            elif not cmd:
paul@1375 994
                                pass
paul@1375 995
                            else:
paul@1375 996
                                continue
paul@1375 997
                            break
paul@1375 998
paul@1375 999
                    cl.show_attendees()
paul@1375 1000
                    print
paul@1375 1001
paul@1375 1002
                # Add or edit period.
paul@1375 1003
paul@1375 1004
                elif args[0] in ("p", "period"):
paul@1375 1005
paul@1375 1006
                    args = args[1:]
paul@1375 1007
                    value = next_arg(args)
paul@1375 1008
paul@1375 1009
                    if value and value.isdigit():
paul@1375 1010
                        index = int(value)
paul@1375 1011
                    else:
paul@1375 1012
                        index = None
paul@1375 1013
paul@1375 1014
                    # Add a new period.
paul@1375 1015
paul@1375 1016
                    if index is None:
paul@1375 1017
                        cl.add_period()
paul@1375 1018
                        cl.edit_period(-1)
paul@1375 1019
paul@1375 1020
                    # Edit period (using index).
paul@1375 1021
paul@1375 1022
                    else:
paul@1375 1023
                        period = cl.can_edit_period(index)
paul@1375 1024
                        if period:
paul@1375 1025
                            while True:
paul@1375 1026
                                show_period_raw(period)
paul@1375 1027
paul@1375 1028
                                # Obtain a command from any arguments.
paul@1375 1029
paul@1375 1030
                                cmd = next_arg(args)
paul@1375 1031
                                if not cmd:
paul@1375 1032
                                    cmd = read_input("  (e)dit, (c)ancel, (u)ncancel (or return)> ")
paul@1375 1033
                                if cmd in ("e", "edit"):
paul@1375 1034
                                    cl.edit_period(index, args)
paul@1375 1035
                                elif cmd in ("c", "cancel"):
paul@1375 1036
                                    cl.cancel_periods([index])
paul@1375 1037
                                elif cmd in ("u", "uncancel", "restore"):
paul@1375 1038
                                    cl.cancel_periods([index], False)
paul@1375 1039
                                elif not cmd:
paul@1375 1040
                                    pass
paul@1375 1041
                                else:
paul@1375 1042
                                    continue
paul@1375 1043
                                break
paul@1375 1044
paul@1375 1045
                    cl.show_periods()
paul@1375 1046
                    print
paul@1375 1047
paul@1375 1048
                # Apply suggested period (using index).
paul@1375 1049
paul@1375 1050
                elif args[0] in ("ps", "period-suggested", "suggested-period"):
paul@1375 1051
                    try:
paul@1375 1052
                        index = int(args[1])
paul@1375 1053
                        cl.apply_suggested_period(index)
paul@1375 1054
                    except ValueError:
paul@1375 1055
                        pass
paul@1375 1056
                    cl.show_periods()
paul@1375 1057
                    print
paul@1375 1058
paul@1396 1059
                # Specify a recurrence rule.
paul@1396 1060
paul@1396 1061
                elif args[0] == "rrule":
paul@1396 1062
                    pass
paul@1396 1063
paul@1375 1064
                # Set the summary.
paul@1375 1065
paul@1375 1066
                elif args[0] in ("s", "summary"):
paul@1387 1067
                    t = cmd.split(None, 1)
paul@1387 1068
                    cl.edit_summary(len(t) > 1 and t[1] or None)
paul@1375 1069
                    cl.show_object()
paul@1375 1070
                    print
paul@1375 1071
paul@1375 1072
    except EOFError:
paul@1375 1073
        return
paul@1375 1074
paul@1375 1075
def main(args):
paul@1375 1076
    global echo
paul@1375 1077
paul@1387 1078
    if "--help" in args:
paul@1387 1079
        show_help(os.path.split(sys.argv[0])[-1])
paul@1387 1080
        return
paul@1387 1081
paul@1375 1082
    # Parse command line arguments using the standard options plus some extra
paul@1375 1083
    # options.
paul@1375 1084
paul@1375 1085
    args = parse_args(args, {
paul@1387 1086
        "--calendar-data"   : ("calendar_data", False),
paul@1387 1087
        "--charset"         : ("charset", "utf-8"),
paul@1375 1088
        "--echo"            : ("echo", False),
paul@1375 1089
        "-f"                : ("filename", None),
paul@1387 1090
        "--handle-data"     : ("handle_data", False),
paul@1375 1091
        "--suppress-bcc"    : ("suppress_bcc", False),
paul@1375 1092
        "-u"                : ("user", None),
paul@1375 1093
        })
paul@1375 1094
paul@1387 1095
    charset = args["charset"]
paul@1387 1096
    calendar_data = args["calendar_data"]
paul@1375 1097
    echo = args["echo"]
paul@1375 1098
    filename = args["filename"]
paul@1387 1099
    handle_data = args["handle_data"]
paul@1375 1100
    sender = (args["senders"] or [None])[0]
paul@1375 1101
    suppress_bcc = args["suppress_bcc"]
paul@1375 1102
    user = args["user"]
paul@1375 1103
paul@1375 1104
    # Determine the user and sender identities.
paul@1375 1105
paul@1375 1106
    if sender and not user:
paul@1375 1107
        user = get_uri(sender)
paul@1375 1108
    elif user and not sender:
paul@1375 1109
        sender = get_address(user)
paul@1375 1110
    elif not sender and not user:
paul@1375 1111
        print >>sys.stderr, "A sender or a user must be specified."
paul@1375 1112
        sys.exit(1)
paul@1375 1113
paul@1375 1114
    # Open a store.
paul@1375 1115
paul@1375 1116
    store_type = args.get("store_type")
paul@1375 1117
    store_dir = args.get("store_dir")
paul@1375 1118
    preferences_dir = args.get("preferences_dir")
paul@1375 1119
paul@1375 1120
    store = get_store(store_type, store_dir)
paul@1375 1121
    journal = None
paul@1375 1122
paul@1375 1123
    # Open a messenger for the user.
paul@1375 1124
paul@1375 1125
    messenger = Messenger(sender=sender, suppress_bcc=suppress_bcc)
paul@1375 1126
paul@1375 1127
    # Open a client for the user.
paul@1375 1128
paul@1375 1129
    cl = TextClient(user, messenger, store, journal, preferences_dir)
paul@1375 1130
paul@1375 1131
    # Read any input resource.
paul@1375 1132
paul@1375 1133
    if filename:
paul@1387 1134
        if calendar_data:
paul@1387 1135
            all_itip = get_itip_from_data(filename, charset)
paul@1387 1136
        else:
paul@1387 1137
            all_itip = get_itip_from_message(filename)
paul@1387 1138
paul@1387 1139
        objects = []
paul@1387 1140
paul@1387 1141
        # Process the objects using the person handler.
paul@1387 1142
paul@1387 1143
        if handle_data:
paul@1387 1144
            for itip in all_itip:
paul@1387 1145
                objects += handle_calendar_data(itip, get_handlers(cl, person.handlers, None))
paul@1387 1146
paul@1387 1147
        # Or just obtain objects from the data.
paul@1387 1148
paul@1387 1149
        else:
paul@1387 1150
            for itip in all_itip:
paul@1387 1151
                objects += get_objects_from_itip(itip, ["VEVENT"])
paul@1375 1152
paul@1375 1153
        # Choose an object to edit.
paul@1375 1154
paul@1375 1155
        show_objects(objects, user, store)
paul@1375 1156
        obj = select_object(cl, objects)
paul@1375 1157
paul@1375 1158
        # Exit without any object.
paul@1375 1159
paul@1375 1160
        if not obj:
paul@1387 1161
            print >>sys.stderr, "No object loaded."
paul@1375 1162
            sys.exit(1)
paul@1375 1163
paul@1375 1164
    # Or create a new object.
paul@1375 1165
paul@1375 1166
    else:
paul@1375 1167
        obj = cl.new_object()
paul@1375 1168
paul@1375 1169
    # Edit the object.
paul@1375 1170
paul@1387 1171
    edit_object(cl, obj, handle_outgoing=handle_data)
paul@1387 1172
paul@1387 1173
def show_help(progname):
paul@1387 1174
    print >>sys.stderr, help_text % progname
paul@1387 1175
paul@1387 1176
help_text = """\
paul@1387 1177
Usage: %s -s <sender> | -u <user> \\
paul@1387 1178
         [ -f <filename> ] \\
paul@1387 1179
         [ --calendar-data --charset ] \\
paul@1387 1180
         [ --handle-data ] \\
paul@1387 1181
         [ -T <store type ] [ -S <store directory> ] \\
paul@1387 1182
         [ -p <preferences directory> ] \\
paul@1387 1183
         [ --echo ]
paul@1387 1184
paul@1387 1185
Identity options:
paul@1387 1186
paul@1387 1187
-s  Indicate the user by specifying a sender address
paul@1387 1188
-u  Indicate the user by specifying their URI
paul@1387 1189
paul@1387 1190
Input options:
paul@1387 1191
paul@1387 1192
-f  Indicates a filename containing a MIME-encoded message or calendar object
paul@1387 1193
paul@1387 1194
--calendar-data  Indicates that the specified file contains a calendar object
paul@1387 1195
                 as opposed to a mail message
paul@1387 1196
--charset        Specifies the character encoding used by a calendar object
paul@1387 1197
                 description
paul@1387 1198
paul@1387 1199
Processing options:
paul@1387 1200
paul@1387 1201
--handle-data    Cause the input to be handled and stored in the configured
paul@1387 1202
                 data store
paul@1387 1203
paul@1387 1204
Configuration options (overriding configured defaults):
paul@1387 1205
paul@1387 1206
-p  Indicates the location of user preference directories
paul@1387 1207
-S  Indicates the location of the calendar data store containing user storage
paul@1387 1208
    directories
paul@1387 1209
-T  Indicates the store type (the configured value if omitted)
paul@1387 1210
paul@1387 1211
Output options:
paul@1387 1212
paul@1387 1213
--echo           Echo received input, useful if consuming input from the
paul@1387 1214
                 standard input stream and producing a log of the program's
paul@1387 1215
                 activity
paul@1387 1216
"""
paul@1375 1217
paul@1375 1218
if __name__ == "__main__":
paul@1375 1219
    main(sys.argv[1:])
paul@1375 1220
paul@1375 1221
# vim: tabstop=4 expandtab shiftwidth=4