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 |