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@1436 | 6 | Copyright (C) 2017, 2018 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@1423 | 27 | is_cancel_itip, parse_itip_part |
paul@1436 | 28 | from imiptools.data import get_address, get_periods_using_selector, \ |
paul@1436 | 29 | get_main_period, get_recurrence_periods, \ |
paul@1436 | 30 | get_value, parse_object |
paul@1375 | 31 | from imiptools.dates import get_datetime_item, get_time, to_timezone |
paul@1436 | 32 | from imiptools.editing import EditingClient, PeriodError, \ |
paul@1436 | 33 | form_periods_from_periods |
paul@1387 | 34 | from imiptools.handlers import person, person_outgoing |
paul@1375 | 35 | from imiptools.mail import Messenger |
paul@1375 | 36 | from imiptools.stores import get_journal, get_store |
paul@1375 | 37 | from imiptools.utils import decode_part, message_as_string |
paul@1396 | 38 | import vRecurrence |
paul@1387 | 39 | import sys, os |
paul@1375 | 40 | |
paul@1439 | 41 | # User interface definitions. |
paul@1439 | 42 | |
paul@1442 | 43 | NEW_COMMANDS = ("n", "new") |
paul@1442 | 44 | QUIT_COMMANDS = ("q", "quit", "exit") |
paul@1442 | 45 | |
paul@1439 | 46 | ATTENDANCE_COMMANDS = ("A", "attend", "attendance") |
paul@1439 | 47 | ATTENDEE_COMMANDS = ("a", "attendee") |
paul@1457 | 48 | CANCEL_PERIOD_COMMANDS = ("c", "cancel") |
paul@1439 | 49 | CHANGE_COMMANDS = ("C", "changes") |
paul@1439 | 50 | CLASSIFICATION_COMMANDS = ("c", "class", "classification") |
paul@1439 | 51 | FINISH_COMMANDS = ("f", "finish") |
paul@1439 | 52 | HELP_COMMANDS = ("h", "help", "?") |
paul@1439 | 53 | LIST_COMMANDS = ("l", "list", "show") |
paul@1439 | 54 | OPERATION_COMMANDS = ("o", "ops", "operations") |
paul@1439 | 55 | PERIOD_COMMANDS = ("p", "period") |
paul@1439 | 56 | RECURRENCEID_COMMANDS = ("RECURRENCE-ID", "RID") |
paul@1439 | 57 | RESET_COMMANDS = ("r", "reload", "reset", "restart") |
paul@1439 | 58 | RULE_COMMANDS = ("rr", "rule", "rrule") |
paul@1439 | 59 | SUGGESTED_ATTENDEE_COMMANDS = ("as", "attendee-suggested", "suggested-attendee") |
paul@1439 | 60 | SUGGESTED_PERIOD_COMMANDS = ("ps", "period-suggested", "suggested-period") |
paul@1439 | 61 | SUMMARY_COMMANDS = ("s", "summary") |
paul@1439 | 62 | UID_COMMANDS = ("UID",) |
paul@1439 | 63 | UNCANCEL_COMMANDS = ("u", "uncancel", "restore") |
paul@1439 | 64 | |
paul@1439 | 65 | CANCEL_COMMANDS = ("R", "remove", "cancel") |
paul@1457 | 66 | CANCEL_PUBLISH_COMMANDS = ("RP", "remove-publish", "cancel-publish") |
paul@1458 | 67 | FREEBUSY_COMMANDS = ("F", "fb", "freebusy", "free/busy", "free-busy") |
paul@1439 | 68 | PUBLISH_COMMANDS = ("P", "publish") |
paul@1439 | 69 | SEND_COMMANDS = ("S", "send") |
paul@1439 | 70 | UPDATE_COMMANDS = ("U", "update") |
paul@1439 | 71 | |
paul@1439 | 72 | EDIT_COMMANDS = ("e", "edit") |
paul@1439 | 73 | REMOVE_COMMANDS = ("r", "remove") |
paul@1439 | 74 | |
paul@1439 | 75 | ACCEPTED_VALUES = ("a", "accept", "accepted", "attend") |
paul@1439 | 76 | DECLINED_VALUES = ("d", "decline", "declined") |
paul@1439 | 77 | TENTATIVE_VALUES = ("t", "tentative") |
paul@1439 | 78 | |
paul@1442 | 79 | COUNT_COMMANDS = ("c", "count", "limit") |
paul@1442 | 80 | FREQUENCY_COMMANDS = ("f", "freq", "frequency") |
paul@1442 | 81 | SELECTION_COMMANDS = ("s", "select", "selection") |
paul@1442 | 82 | |
paul@1442 | 83 | YEARLY_VALUES = ("y", "year", "yearly") |
paul@1442 | 84 | MONTHLY_VALUES = ("month", "monthly") |
paul@1442 | 85 | WEEKLY_VALUES = ("w", "week", "weekly") |
paul@1442 | 86 | DAILY_VALUES = ("d", "day", "daily") |
paul@1442 | 87 | HOURLY_VALUES = ("h", "hour", "hourly") |
paul@1442 | 88 | MINUTELY_VALUES = ("minute", "minutely") |
paul@1442 | 89 | SECONDLY_VALUES = ("s", "second", "secondly") |
paul@1442 | 90 | |
paul@1442 | 91 | MONTH_VALUES = ("month", "months") |
paul@1442 | 92 | WEEK_VALUES = ("w", "week", "weeks") |
paul@1442 | 93 | YEARDAY_VALUES = ("y", "yearday", "yeardays") |
paul@1442 | 94 | MONTHDAY_VALUES = ("o", "monthday", "monthdays") |
paul@1442 | 95 | DAY_VALUES = ("d", "weekday", "weekdays") |
paul@1442 | 96 | HOUR_VALUES = ("h", "hour", "hours") |
paul@1442 | 97 | MINUTE_VALUES = ("m", "minute", "minutes") |
paul@1442 | 98 | SECOND_VALUES = ("s", "second", "seconds") |
paul@1442 | 99 | |
paul@1439 | 100 | def commandlist(l, option=None): |
paul@1439 | 101 | |
paul@1439 | 102 | "Show 'l' as a command list string employing any given 'option'." |
paul@1439 | 103 | |
paul@1439 | 104 | if option: |
paul@1439 | 105 | l2 = [] |
paul@1439 | 106 | for s in l: |
paul@1439 | 107 | l2.append("%s %s" % (s, option)) |
paul@1439 | 108 | return "\n".join(l2) |
paul@1439 | 109 | return ", ".join(l) |
paul@1439 | 110 | |
paul@1375 | 111 | # User interface functions. |
paul@1375 | 112 | |
paul@1375 | 113 | echo = False |
paul@1375 | 114 | |
paul@1375 | 115 | def read_input(label): |
paul@1396 | 116 | |
paul@1396 | 117 | """ |
paul@1396 | 118 | Read input, prompting using 'label', stripping leading and trailing |
paul@1396 | 119 | whitespace, echoing the input if the global 'echo' variable is set. |
paul@1396 | 120 | """ |
paul@1396 | 121 | |
paul@1375 | 122 | s = raw_input(label).strip() |
paul@1375 | 123 | if echo: |
paul@1375 | 124 | print s |
paul@1375 | 125 | return s |
paul@1375 | 126 | |
paul@1375 | 127 | def input_with_default(label, default): |
paul@1396 | 128 | |
paul@1396 | 129 | """ |
paul@1396 | 130 | Read input, prompting using 'label', parameterising the label with the given |
paul@1396 | 131 | 'default' and returning the default if no input is given. |
paul@1396 | 132 | """ |
paul@1396 | 133 | |
paul@1375 | 134 | return read_input(label % default) or default |
paul@1375 | 135 | |
paul@1436 | 136 | def to_int_or_none(value): |
paul@1436 | 137 | |
paul@1436 | 138 | "Return 'value' as an integer or None for other inputs." |
paul@1436 | 139 | |
paul@1436 | 140 | try: |
paul@1436 | 141 | return int(value) |
paul@1436 | 142 | except (TypeError, ValueError): |
paul@1436 | 143 | return None |
paul@1436 | 144 | |
paul@1436 | 145 | def format_value_ranges(ranges, null_value="-"): |
paul@1436 | 146 | |
paul@1436 | 147 | "Format 'ranges' as a single descriptive string." |
paul@1436 | 148 | |
paul@1436 | 149 | l = [] |
paul@1436 | 150 | |
paul@1436 | 151 | for value_range in ranges: |
paul@1436 | 152 | if isinstance(value_range, tuple): |
paul@1436 | 153 | start, end = value_range |
paul@1436 | 154 | l.append("%s...%s" % (start, end)) |
paul@1436 | 155 | elif isinstance(value_range, list): |
paul@1436 | 156 | l.append(", ".join(value_range)) |
paul@1436 | 157 | elif isinstance(value_range, dict): |
paul@1436 | 158 | l.append(", ".join(value_range.keys())) |
paul@1436 | 159 | else: |
paul@1436 | 160 | l.append(value_range or null_value) |
paul@1436 | 161 | |
paul@1436 | 162 | return ", ".join(l) |
paul@1436 | 163 | |
paul@1375 | 164 | def print_title(text): |
paul@1396 | 165 | |
paul@1396 | 166 | "Print 'text' with simple, fixed-width styling as a title." |
paul@1396 | 167 | |
paul@1375 | 168 | print text |
paul@1375 | 169 | print len(text) * "-" |
paul@1375 | 170 | |
paul@1396 | 171 | def print_table(rows, separator_index=0): |
paul@1396 | 172 | |
paul@1396 | 173 | """ |
paul@1396 | 174 | Print 'rows' as a simple, fixed-width table. If 'separator_index' is set to |
paul@1396 | 175 | a row index present in the rows, a table separator will be produced below |
paul@1396 | 176 | that row's data. Otherwise, a separator will appear below the first row. |
paul@1396 | 177 | """ |
paul@1396 | 178 | |
paul@1396 | 179 | widths = [] |
paul@1396 | 180 | for row in rows: |
paul@1396 | 181 | for i, col in enumerate(row): |
paul@1396 | 182 | if i >= len(widths): |
paul@1396 | 183 | widths.append(len(col)) |
paul@1396 | 184 | else: |
paul@1396 | 185 | widths[i] = max(widths[i], len(col)) |
paul@1396 | 186 | |
paul@1396 | 187 | for i, row in enumerate(rows): |
paul@1396 | 188 | for col, width in zip(row, widths): |
paul@1396 | 189 | print "%s%s" % (col, " " * (width - len(col))), |
paul@1396 | 190 | print |
paul@1396 | 191 | if i == separator_index: |
paul@1396 | 192 | for width in widths: |
paul@1396 | 193 | print "-" * width, |
paul@1396 | 194 | print |
paul@1396 | 195 | |
paul@1375 | 196 | def write(s, filename): |
paul@1396 | 197 | |
paul@1396 | 198 | "Write 's' to a file having the given 'filename'." |
paul@1396 | 199 | |
paul@1375 | 200 | f = filename and open(filename, "w") or None |
paul@1375 | 201 | try: |
paul@1375 | 202 | print >>(f or sys.stdout), s |
paul@1375 | 203 | finally: |
paul@1375 | 204 | if f: |
paul@1375 | 205 | f.close() |
paul@1375 | 206 | |
paul@1375 | 207 | # Interpret an input file containing a calendar resource. |
paul@1375 | 208 | |
paul@1387 | 209 | def get_itip_from_message(filename): |
paul@1375 | 210 | |
paul@1387 | 211 | "Return iTIP details provided by 'filename'." |
paul@1375 | 212 | |
paul@1375 | 213 | f = open(filename) |
paul@1375 | 214 | try: |
paul@1375 | 215 | msg = message_from_file(f) |
paul@1375 | 216 | finally: |
paul@1375 | 217 | f.close() |
paul@1375 | 218 | |
paul@1387 | 219 | all_itip = [] |
paul@1375 | 220 | |
paul@1375 | 221 | for part in msg.walk(): |
paul@1387 | 222 | if have_itip_part(part): |
paul@1387 | 223 | all_itip.append(parse_itip_part(part)) |
paul@1375 | 224 | |
paul@1387 | 225 | return all_itip |
paul@1387 | 226 | |
paul@1387 | 227 | def get_itip_from_data(filename, charset): |
paul@1375 | 228 | |
paul@1387 | 229 | "Return objects provided by 'filename'." |
paul@1375 | 230 | |
paul@1387 | 231 | f = open(filename) |
paul@1387 | 232 | try: |
paul@1387 | 233 | itip = parse_object(f, charset, "VCALENDAR") |
paul@1387 | 234 | finally: |
paul@1387 | 235 | f.close() |
paul@1387 | 236 | |
paul@1387 | 237 | return [itip] |
paul@1375 | 238 | |
paul@1436 | 239 | # Object and request display. |
paul@1436 | 240 | |
paul@1375 | 241 | def show_objects(objects, user, store): |
paul@1375 | 242 | |
paul@1375 | 243 | """ |
paul@1375 | 244 | Show details of 'objects', accessed by the given 'user' in the given |
paul@1375 | 245 | 'store'. |
paul@1375 | 246 | """ |
paul@1375 | 247 | |
paul@1387 | 248 | print |
paul@1387 | 249 | print_title("Objects") |
paul@1387 | 250 | print |
paul@1387 | 251 | |
paul@1375 | 252 | for index, obj in enumerate(objects): |
paul@1375 | 253 | recurrenceid = obj.get_recurrenceid() |
paul@1375 | 254 | recurrence_label = recurrenceid and " %s" % recurrenceid or "" |
paul@1375 | 255 | print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label) |
paul@1375 | 256 | |
paul@1387 | 257 | def show_requests(user, store): |
paul@1387 | 258 | |
paul@1387 | 259 | "Show requests available to the given 'user' in the given 'store'." |
paul@1387 | 260 | |
paul@1387 | 261 | requests = store.get_requests(user) |
paul@1387 | 262 | |
paul@1387 | 263 | print |
paul@1387 | 264 | print_title("Requests") |
paul@1387 | 265 | print |
paul@1387 | 266 | |
paul@1387 | 267 | if not requests: |
paul@1387 | 268 | print "No requests are pending." |
paul@1387 | 269 | return |
paul@1387 | 270 | |
paul@1387 | 271 | for index, (uid, recurrenceid) in enumerate(requests): |
paul@1387 | 272 | obj = store.get_event(user, uid, recurrenceid) |
paul@1387 | 273 | recurrence_label = recurrenceid and " %s" % recurrenceid or "" |
paul@1387 | 274 | print "(%d) Summary: %s (%s%s)" % (index, obj.get_value("SUMMARY"), obj.get_uid(), recurrence_label) |
paul@1387 | 275 | |
paul@1436 | 276 | # Object details display. |
paul@1436 | 277 | |
paul@1375 | 278 | def show_attendee(attendee_item, index): |
paul@1375 | 279 | |
paul@1375 | 280 | "Show the 'attendee_item' (value and attributes) at 'index'." |
paul@1375 | 281 | |
paul@1375 | 282 | attendee, attr = attendee_item |
paul@1375 | 283 | partstat = attr.get("PARTSTAT") |
paul@1375 | 284 | print "(%d) %s%s" % (index, attendee, partstat and " (%s)" % partstat or "") |
paul@1375 | 285 | |
paul@1375 | 286 | def show_attendees_raw(attendee_map): |
paul@1375 | 287 | |
paul@1375 | 288 | "Show the 'attendee_map' in a simple raw form." |
paul@1375 | 289 | |
paul@1375 | 290 | for attendee, attr in attendee_map.items(): |
paul@1375 | 291 | print attendee |
paul@1375 | 292 | |
paul@1375 | 293 | def show_periods(periods, errors=None): |
paul@1375 | 294 | |
paul@1375 | 295 | "Show 'periods' with any indicated 'errors'." |
paul@1375 | 296 | |
paul@1375 | 297 | main = get_main_period(periods) |
paul@1375 | 298 | if main: |
paul@1375 | 299 | show_period(main, 0, errors) |
paul@1375 | 300 | |
paul@1375 | 301 | recurrences = get_recurrence_periods(periods) |
paul@1375 | 302 | if recurrences: |
paul@1375 | 303 | print |
paul@1375 | 304 | print_title("Recurrences") |
paul@1375 | 305 | for index, p in enumerate(recurrences): |
paul@1375 | 306 | show_period(p, index + 1, errors) |
paul@1375 | 307 | |
paul@1375 | 308 | def show_period(p, index, errors=None): |
paul@1375 | 309 | |
paul@1375 | 310 | "Show period 'p' at 'index' with any indicated 'errors'." |
paul@1375 | 311 | |
paul@1375 | 312 | errors = errors and errors.get(index) |
paul@1422 | 313 | if p.cancelled: |
paul@1422 | 314 | label = "Cancelled" |
paul@1422 | 315 | elif p.replacement: |
paul@1422 | 316 | label = "Replaced" |
paul@1422 | 317 | elif p.new_replacement: |
paul@1422 | 318 | label = "To replace" |
paul@1422 | 319 | elif p.recurrenceid: |
paul@1422 | 320 | label = "Retained" |
paul@1375 | 321 | else: |
paul@1422 | 322 | label = "New" |
paul@1375 | 323 | |
paul@1375 | 324 | error_label = errors and " (errors: %s)" % ", ".join(errors) or "" |
paul@1453 | 325 | print "(%d) %s%s:" % (index, label, error_label), \ |
paul@1453 | 326 | str(p.get_form_start()), str(p.get_form_end()), p.origin |
paul@1375 | 327 | |
paul@1375 | 328 | def show_periods_raw(periods): |
paul@1375 | 329 | |
paul@1375 | 330 | "Show 'periods' in a simple raw form." |
paul@1375 | 331 | |
paul@1375 | 332 | periods = periods[:] |
paul@1375 | 333 | periods.sort() |
paul@1375 | 334 | map(show_period_raw, periods) |
paul@1375 | 335 | |
paul@1375 | 336 | def show_period_raw(p): |
paul@1375 | 337 | |
paul@1375 | 338 | "Show period 'p' in a simple raw form." |
paul@1375 | 339 | |
paul@1453 | 340 | print str(p.get_form_start()), str(p.get_form_end()), p.origin |
paul@1375 | 341 | |
paul@1436 | 342 | def show_rule(selectors): |
paul@1396 | 343 | |
paul@1436 | 344 | "Show recurrence rule specification 'selectors'." |
paul@1396 | 345 | |
paul@1396 | 346 | # Collect limit, selection and frequency details. |
paul@1396 | 347 | |
paul@1436 | 348 | for i, selector in enumerate(selectors): |
paul@1436 | 349 | prefix = "(%d) " % i |
paul@1436 | 350 | show_rule_selector(selector, prefix) |
paul@1396 | 351 | |
paul@1436 | 352 | print |
paul@1436 | 353 | print vRecurrence.to_string(selectors) |
paul@1396 | 354 | |
paul@1436 | 355 | def show_rule_selector(selector, prefix=""): |
paul@1436 | 356 | |
paul@1436 | 357 | "Show the rule 'selector', employing any given 'prefix' for formatting." |
paul@1396 | 358 | |
paul@1436 | 359 | # COUNT |
paul@1396 | 360 | |
paul@1436 | 361 | if isinstance(selector, vRecurrence.LimitSelector): |
paul@1436 | 362 | print "%sAt most %d occurrences" % (prefix, selector.args["values"][0]) |
paul@1436 | 363 | |
paul@1436 | 364 | # BYSETPOS |
paul@1396 | 365 | |
paul@1436 | 366 | elif isinstance(selector, vRecurrence.PositionSelector): |
paul@1436 | 367 | for value in selector.get_positions(): |
paul@1436 | 368 | print "%sSelect occurrence #%d" % (prefix, value) |
paul@1436 | 369 | prefix = len(prefix) * " " |
paul@1396 | 370 | |
paul@1436 | 371 | # BYWEEKDAY |
paul@1396 | 372 | |
paul@1436 | 373 | elif isinstance(selector, vRecurrence.WeekDayFilter): |
paul@1436 | 374 | for value, index in selector.get_values(): |
paul@1436 | 375 | print "%sSelect occurrence #%d (from %s) of weekday %s" % ( |
paul@1436 | 376 | prefix, abs(index), index >= 0 and "start" or "end", |
paul@1436 | 377 | get_weekday(value)) |
paul@1436 | 378 | prefix = len(prefix) * " " |
paul@1396 | 379 | |
paul@1436 | 380 | # BY... |
paul@1396 | 381 | |
paul@1436 | 382 | elif isinstance(selector, vRecurrence.Enum): |
paul@1436 | 383 | for value in selector.get_values(): |
paul@1436 | 384 | print "%sSelect %s %r" % (prefix, get_resolution(selector.level), |
paul@1436 | 385 | value) |
paul@1436 | 386 | prefix = len(prefix) * " " |
paul@1436 | 387 | |
paul@1436 | 388 | # YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY |
paul@1396 | 389 | |
paul@1436 | 390 | elif isinstance(selector, vRecurrence.Pattern): |
paul@1436 | 391 | print "%sEach %s with interval %d" % (prefix, |
paul@1436 | 392 | get_resolution(selector.level), selector.args.get("interval", 1)) |
paul@1396 | 393 | |
paul@1436 | 394 | def get_resolution(level): |
paul@1396 | 395 | |
paul@1436 | 396 | "Return a textual description of the given resolution 'level'." |
paul@1436 | 397 | |
paul@1436 | 398 | levels = ["year", "month", "week", "day in year", "day in month", "day", "hour", "minute", "second"] |
paul@1396 | 399 | return levels[level] |
paul@1396 | 400 | |
paul@1396 | 401 | def get_weekday(weekday): |
paul@1436 | 402 | |
paul@1436 | 403 | "Return the name of the given 1-based 'weekday' number." |
paul@1436 | 404 | |
paul@1396 | 405 | weekdays = [None, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] |
paul@1396 | 406 | return weekdays[weekday] |
paul@1396 | 407 | |
paul@1375 | 408 | def show_attendee_changes(new, modified, unmodified, removed): |
paul@1375 | 409 | |
paul@1375 | 410 | "Show 'new', 'modified', 'unmodified' and 'removed' periods." |
paul@1375 | 411 | |
paul@1375 | 412 | print |
paul@1375 | 413 | print_title("Changes to attendees") |
paul@1375 | 414 | print |
paul@1375 | 415 | print "New:" |
paul@1375 | 416 | show_attendees_raw(new) |
paul@1375 | 417 | print |
paul@1375 | 418 | print "Modified:" |
paul@1375 | 419 | show_attendees_raw(modified) |
paul@1375 | 420 | print |
paul@1375 | 421 | print "Unmodified:" |
paul@1375 | 422 | show_attendees_raw(unmodified) |
paul@1375 | 423 | print |
paul@1375 | 424 | print "Removed:" |
paul@1375 | 425 | show_attendees_raw(removed) |
paul@1375 | 426 | |
paul@1375 | 427 | def show_period_classification(new, replaced, retained, cancelled, obsolete): |
paul@1375 | 428 | |
paul@1375 | 429 | "Show 'new', 'replaced', 'retained', 'cancelled' and 'obsolete' periods." |
paul@1375 | 430 | |
paul@1375 | 431 | print |
paul@1375 | 432 | print_title("Period classification") |
paul@1375 | 433 | print |
paul@1375 | 434 | print "New:" |
paul@1375 | 435 | show_periods_raw(new) |
paul@1375 | 436 | print |
paul@1375 | 437 | print "Replaced:" |
paul@1375 | 438 | show_periods_raw(replaced) |
paul@1375 | 439 | print |
paul@1375 | 440 | print "Retained:" |
paul@1375 | 441 | show_periods_raw(retained) |
paul@1375 | 442 | print |
paul@1375 | 443 | print "Cancelled:" |
paul@1375 | 444 | show_periods_raw(cancelled) |
paul@1375 | 445 | print |
paul@1375 | 446 | print "Obsolete:" |
paul@1375 | 447 | show_periods_raw(obsolete) |
paul@1375 | 448 | |
paul@1375 | 449 | def show_changes(modified, unmodified, removed): |
paul@1375 | 450 | |
paul@1375 | 451 | "Show 'modified', 'unmodified' and 'removed' periods." |
paul@1375 | 452 | |
paul@1375 | 453 | print |
paul@1375 | 454 | print_title("Changes to periods") |
paul@1375 | 455 | print |
paul@1375 | 456 | print "Modified:" |
paul@1375 | 457 | show_periods_raw(modified) |
paul@1375 | 458 | print |
paul@1375 | 459 | print "Unmodified:" |
paul@1375 | 460 | show_periods_raw(unmodified) |
paul@1375 | 461 | print |
paul@1375 | 462 | print "Removed:" |
paul@1375 | 463 | show_periods_raw(removed) |
paul@1375 | 464 | |
paul@1375 | 465 | def show_attendee_operations(to_invite, to_cancel, to_modify): |
paul@1375 | 466 | |
paul@1375 | 467 | "Show attendees 'to_invite', 'to_cancel' and 'to_modify'." |
paul@1375 | 468 | |
paul@1375 | 469 | print |
paul@1375 | 470 | print_title("Attendee update operations") |
paul@1375 | 471 | print |
paul@1375 | 472 | print "To invite:" |
paul@1375 | 473 | show_attendees_raw(to_invite) |
paul@1375 | 474 | print |
paul@1375 | 475 | print "To cancel:" |
paul@1375 | 476 | show_attendees_raw(to_cancel) |
paul@1375 | 477 | print |
paul@1375 | 478 | print "To modify:" |
paul@1375 | 479 | show_attendees_raw(to_modify) |
paul@1375 | 480 | |
paul@1375 | 481 | def show_period_operations(to_unschedule, to_reschedule, to_add, to_exclude, to_set, |
paul@1375 | 482 | all_unscheduled, all_rescheduled): |
paul@1375 | 483 | |
paul@1375 | 484 | """ |
paul@1375 | 485 | Show operations for periods 'to_unschedule', 'to_reschedule', 'to_add', |
paul@1375 | 486 | 'to_exclude' and 'to_set' (for updating other calendar participants), and |
paul@1375 | 487 | for periods 'all_unscheduled' and 'all_rescheduled' (for publishing event |
paul@1375 | 488 | state). |
paul@1375 | 489 | """ |
paul@1375 | 490 | |
paul@1375 | 491 | print |
paul@1375 | 492 | print_title("Period update and publishing operations") |
paul@1375 | 493 | print |
paul@1375 | 494 | print "Unschedule:" |
paul@1375 | 495 | show_periods_raw(to_unschedule) |
paul@1375 | 496 | print |
paul@1375 | 497 | print "Reschedule:" |
paul@1375 | 498 | show_periods_raw(to_reschedule) |
paul@1375 | 499 | print |
paul@1375 | 500 | print "Added:" |
paul@1375 | 501 | show_periods_raw(to_add) |
paul@1375 | 502 | print |
paul@1375 | 503 | print "Excluded:" |
paul@1375 | 504 | show_periods_raw(to_exclude) |
paul@1375 | 505 | print |
paul@1375 | 506 | print "Set in object:" |
paul@1375 | 507 | show_periods_raw(to_set) |
paul@1375 | 508 | print |
paul@1375 | 509 | print "All unscheduled:" |
paul@1375 | 510 | show_periods_raw(all_unscheduled) |
paul@1375 | 511 | print |
paul@1375 | 512 | print "All rescheduled:" |
paul@1375 | 513 | show_periods_raw(all_rescheduled) |
paul@1375 | 514 | |
paul@1375 | 515 | class TextClient(EditingClient): |
paul@1375 | 516 | |
paul@1375 | 517 | "Simple client with textual output." |
paul@1375 | 518 | |
paul@1375 | 519 | def new_object(self): |
paul@1375 | 520 | |
paul@1375 | 521 | "Create a new object with the current time." |
paul@1375 | 522 | |
paul@1375 | 523 | utcnow = get_time() |
paul@1375 | 524 | now = to_timezone(utcnow, self.get_tzid()) |
paul@1375 | 525 | obj = EditingClient.new_object(self, "VEVENT") |
paul@1375 | 526 | obj.set_value("SUMMARY", "New event") |
paul@1375 | 527 | obj["DTSTART"] = [get_datetime_item(now)] |
paul@1375 | 528 | obj["DTEND"] = [get_datetime_item(now)] |
paul@1375 | 529 | return obj |
paul@1375 | 530 | |
paul@1436 | 531 | def handle_outgoing_object(self): |
paul@1436 | 532 | |
paul@1436 | 533 | "Handle the current object using the outgoing handlers." |
paul@1436 | 534 | |
paul@1436 | 535 | unscheduled_objects, rescheduled_objects, added_objects = \ |
paul@1436 | 536 | self.get_publish_objects() |
paul@1436 | 537 | |
paul@1436 | 538 | handlers = get_handlers(self, person_outgoing.handlers, |
paul@1436 | 539 | [get_address(self.user)]) |
paul@1436 | 540 | |
paul@1436 | 541 | # Handle the parent object plus any altered periods. |
paul@1436 | 542 | |
paul@1436 | 543 | handle_calendar_object(self.obj, handlers, "PUBLISH") |
paul@1436 | 544 | |
paul@1436 | 545 | for o in unscheduled_objects: |
paul@1436 | 546 | handle_calendar_object(o, handlers, "CANCEL") |
paul@1436 | 547 | |
paul@1436 | 548 | for o in rescheduled_objects: |
paul@1436 | 549 | handle_calendar_object(o, handlers, "PUBLISH") |
paul@1436 | 550 | |
paul@1436 | 551 | for o in added_objects: |
paul@1436 | 552 | handle_calendar_object(o, handlers, "ADD") |
paul@1436 | 553 | |
paul@1436 | 554 | def update_periods_from_rule(self): |
paul@1436 | 555 | |
paul@1436 | 556 | "Update the periods from the rule." |
paul@1436 | 557 | |
paul@1436 | 558 | selectors = self.state.get("rule") |
paul@1436 | 559 | periods = self.state.get("periods") |
paul@1436 | 560 | |
paul@1436 | 561 | main_period = get_main_period(periods) |
paul@1436 | 562 | tzid = main_period.get_tzid() |
paul@1436 | 563 | |
paul@1436 | 564 | start = main_period.get_start() |
paul@1436 | 565 | end = self.get_window_end() or None |
paul@1436 | 566 | |
paul@1436 | 567 | selector = vRecurrence.get_selector(start, selectors) |
paul@1436 | 568 | inclusive = False |
paul@1436 | 569 | |
paul@1436 | 570 | # Generate the periods from the rule. |
paul@1436 | 571 | |
paul@1436 | 572 | rule_periods = form_periods_from_periods( |
paul@1436 | 573 | get_periods_using_selector(selector, main_period, tzid, |
paul@1436 | 574 | start, end, inclusive)) |
paul@1436 | 575 | |
paul@1436 | 576 | # Retain any applicable replacement periods that either modify or cancel |
paul@1436 | 577 | # rule periods. |
paul@1436 | 578 | # NOTE: To be done. |
paul@1436 | 579 | |
paul@1436 | 580 | self.state.set("periods", rule_periods) |
paul@1436 | 581 | |
paul@1375 | 582 | # Editing methods involving interaction. |
paul@1375 | 583 | |
paul@1436 | 584 | def add_rule_selectors(self): |
paul@1436 | 585 | |
paul@1436 | 586 | "Add rule selectors to the rule details." |
paul@1436 | 587 | |
paul@1436 | 588 | selectors = self.state.get("rule") |
paul@1436 | 589 | |
paul@1436 | 590 | while True: |
paul@1436 | 591 | |
paul@1436 | 592 | # Obtain a command from any arguments. |
paul@1436 | 593 | |
paul@1436 | 594 | s = read_input("Selector: (c)ount, (f)requency, (s)election (or return)> ") |
paul@1436 | 595 | args = s.split() |
paul@1436 | 596 | cmd = next_arg(args) |
paul@1436 | 597 | |
paul@1442 | 598 | if cmd in COUNT_COMMANDS: |
paul@1436 | 599 | add_rule_selector_count(selectors, args) |
paul@1442 | 600 | elif cmd in FREQUENCY_COMMANDS: |
paul@1436 | 601 | add_rule_selector_frequency(selectors, args) |
paul@1442 | 602 | elif cmd in SELECTION_COMMANDS: |
paul@1436 | 603 | add_rule_selector_selection(selectors, args) |
paul@1436 | 604 | |
paul@1436 | 605 | # Remain in the loop unless explicitly terminated. |
paul@1436 | 606 | |
paul@1436 | 607 | elif not cmd or cmd == "end": |
paul@1436 | 608 | break |
paul@1436 | 609 | |
paul@1375 | 610 | def edit_attendee(self, index): |
paul@1375 | 611 | |
paul@1375 | 612 | "Edit the attendee at 'index'." |
paul@1375 | 613 | |
paul@1375 | 614 | t = self.can_edit_attendee(index) |
paul@1439 | 615 | if not t: |
paul@1439 | 616 | return |
paul@1439 | 617 | |
paul@1439 | 618 | attendees = self.state.get("attendees") |
paul@1439 | 619 | old_attendee, attr = t |
paul@1439 | 620 | |
paul@1439 | 621 | attendee = input_with_default("Attendee (%s) (or return)? ", old_attendee) |
paul@1439 | 622 | |
paul@1439 | 623 | # Remove the old attendee if null or a replacement is given. |
paul@1439 | 624 | |
paul@1439 | 625 | if attendee.strip() or not old_attendee: |
paul@1439 | 626 | del attendees[old_attendee] |
paul@1439 | 627 | |
paul@1439 | 628 | # Add any replacement. |
paul@1439 | 629 | |
paul@1439 | 630 | if attendee.strip(): |
paul@1375 | 631 | attendees[attendee] = attr |
paul@1375 | 632 | |
paul@1375 | 633 | def edit_period(self, index, args=None): |
paul@1436 | 634 | |
paul@1436 | 635 | "Edit the period at 'index'." |
paul@1436 | 636 | |
paul@1375 | 637 | period = self.can_edit_period(index) |
paul@1375 | 638 | if period: |
paul@1375 | 639 | edit_period(period, args) |
paul@1375 | 640 | period.cancelled = False |
paul@1422 | 641 | |
paul@1422 | 642 | # Change the origin of modified rule periods. |
paul@1422 | 643 | |
paul@1422 | 644 | if period.origin == "RRULE": |
paul@1422 | 645 | period.origin = "RDATE" |
paul@1375 | 646 | |
paul@1375 | 647 | # Sort the periods after this change. |
paul@1375 | 648 | |
paul@1375 | 649 | periods = self.state.get("periods") |
paul@1375 | 650 | periods.sort() |
paul@1375 | 651 | |
paul@1436 | 652 | def edit_rule_selector(self, index, args): |
paul@1436 | 653 | |
paul@1436 | 654 | "Edit the selector having the given 'index'." |
paul@1436 | 655 | |
paul@1436 | 656 | selectors = self.state.get("rule") |
paul@1436 | 657 | selector = self.can_edit_rule_selector(index) |
paul@1436 | 658 | |
paul@1436 | 659 | if not selector: |
paul@1436 | 660 | return |
paul@1436 | 661 | |
paul@1436 | 662 | while True: |
paul@1436 | 663 | show_rule_selector(selector) |
paul@1436 | 664 | |
paul@1436 | 665 | # Obtain a command from any arguments. |
paul@1436 | 666 | |
paul@1436 | 667 | cmd = next_arg(args) |
paul@1436 | 668 | if not cmd: |
paul@1436 | 669 | s = read_input("Selector: (e)dit, (r)emove (or return)> ") |
paul@1436 | 670 | args = s.split() |
paul@1436 | 671 | cmd = next_arg(args) |
paul@1436 | 672 | |
paul@1436 | 673 | # Edit an existing selector. |
paul@1436 | 674 | |
paul@1439 | 675 | if cmd in EDIT_COMMANDS: |
paul@1436 | 676 | if isinstance(selector, vRecurrence.LimitSelector): |
paul@1436 | 677 | add_rule_selector_count(selectors, args, selector) |
paul@1436 | 678 | elif isinstance(selector, vRecurrence.Pattern): |
paul@1436 | 679 | add_rule_selector_frequency(selectors, args, selector) |
paul@1436 | 680 | else: |
paul@1436 | 681 | add_rule_selector_selection(selectors, args, selector) |
paul@1436 | 682 | |
paul@1436 | 683 | # Remove an existing selector. |
paul@1436 | 684 | |
paul@1439 | 685 | elif cmd in REMOVE_COMMANDS: |
paul@1436 | 686 | del selectors[index] |
paul@1436 | 687 | |
paul@1436 | 688 | # Exit if requested or after a successful |
paul@1436 | 689 | # operation. |
paul@1436 | 690 | |
paul@1436 | 691 | elif not cmd: |
paul@1436 | 692 | pass |
paul@1436 | 693 | else: |
paul@1436 | 694 | continue |
paul@1436 | 695 | break |
paul@1436 | 696 | |
paul@1375 | 697 | def edit_summary(self, summary=None): |
paul@1436 | 698 | |
paul@1436 | 699 | "Edit or set the 'summary'." |
paul@1436 | 700 | |
paul@1375 | 701 | if self.can_edit_properties(): |
paul@1375 | 702 | if not summary: |
paul@1375 | 703 | summary = input_with_default("Summary (%s)? ", self.state.get("summary")) |
paul@1375 | 704 | self.state.set("summary", summary) |
paul@1375 | 705 | |
paul@1375 | 706 | def finish(self): |
paul@1436 | 707 | |
paul@1436 | 708 | "Finish editing, warning of errors if any occur." |
paul@1436 | 709 | |
paul@1375 | 710 | try: |
paul@1375 | 711 | EditingClient.finish(self) |
paul@1375 | 712 | except PeriodError: |
paul@1375 | 713 | print "Errors exist in the periods." |
paul@1375 | 714 | return |
paul@1375 | 715 | |
paul@1375 | 716 | # Diagnostic methods. |
paul@1375 | 717 | |
paul@1375 | 718 | def show_period_classification(self): |
paul@1436 | 719 | |
paul@1436 | 720 | "Show the classification of the periods." |
paul@1436 | 721 | |
paul@1375 | 722 | try: |
paul@1375 | 723 | new, replaced, retained, cancelled, obsolete = self.classify_periods() |
paul@1375 | 724 | show_period_classification(new, replaced, retained, cancelled, obsolete) |
paul@1375 | 725 | except PeriodError: |
paul@1375 | 726 | print |
paul@1375 | 727 | print "Errors exist in the periods." |
paul@1375 | 728 | |
paul@1375 | 729 | def show_changes(self): |
paul@1436 | 730 | |
paul@1436 | 731 | "Show how the periods have changed." |
paul@1436 | 732 | |
paul@1375 | 733 | try: |
paul@1375 | 734 | modified, unmodified, removed = self.classify_period_changes() |
paul@1375 | 735 | show_changes(modified, unmodified, removed) |
paul@1375 | 736 | except PeriodError: |
paul@1375 | 737 | print "Errors exist in the periods." |
paul@1375 | 738 | |
paul@1375 | 739 | is_changed = self.properties_changed() |
paul@1375 | 740 | if is_changed: |
paul@1375 | 741 | print |
paul@1375 | 742 | print "Properties changed:", ", ".join(is_changed) |
paul@1375 | 743 | new, modified, unmodified, removed = self.classify_attendee_changes() |
paul@1375 | 744 | show_attendee_changes(new, modified, unmodified, removed) |
paul@1375 | 745 | |
paul@1375 | 746 | def show_operations(self): |
paul@1436 | 747 | |
paul@1436 | 748 | "Show the operations required to change the periods for recipients." |
paul@1436 | 749 | |
paul@1375 | 750 | is_changed = self.properties_changed() |
paul@1375 | 751 | |
paul@1375 | 752 | try: |
paul@1375 | 753 | to_unschedule, to_reschedule, to_add, to_exclude, to_set, \ |
paul@1375 | 754 | all_unscheduled, all_rescheduled = self.classify_period_operations() |
paul@1375 | 755 | show_period_operations(to_unschedule, to_reschedule, to_add, |
paul@1375 | 756 | to_exclude, to_set, |
paul@1375 | 757 | all_unscheduled, all_rescheduled) |
paul@1375 | 758 | except PeriodError: |
paul@1375 | 759 | print "Errors exist in the periods." |
paul@1375 | 760 | |
paul@1375 | 761 | to_invite, to_cancel, to_modify = self.classify_attendee_operations() |
paul@1375 | 762 | show_attendee_operations(to_invite, to_cancel, to_modify) |
paul@1375 | 763 | |
paul@1375 | 764 | # Output methods. |
paul@1375 | 765 | |
paul@1375 | 766 | def show_message(self, message, plain=False, filename=None): |
paul@1436 | 767 | |
paul@1436 | 768 | """ |
paul@1436 | 769 | Show the given mail 'message', decoding to plain text if 'plain' is set |
paul@1436 | 770 | to a true value, writing it to 'filename' if indicated. |
paul@1436 | 771 | """ |
paul@1436 | 772 | |
paul@1375 | 773 | if plain: |
paul@1375 | 774 | decode_part(message) |
paul@1375 | 775 | write(message_as_string(message), filename) |
paul@1375 | 776 | |
paul@1375 | 777 | def show_cancel_message(self, plain=False, filename=None): |
paul@1375 | 778 | |
paul@1375 | 779 | "Show the cancel message for uninvited attendees." |
paul@1375 | 780 | |
paul@1375 | 781 | message = self.prepare_cancel_message() |
paul@1375 | 782 | if message: |
paul@1375 | 783 | self.show_message(message, plain, filename) |
paul@1375 | 784 | |
paul@1458 | 785 | def show_freebusy_message(self, plain=False, filename=None): |
paul@1458 | 786 | |
paul@1458 | 787 | "Show a free/busy request message for the main period." |
paul@1458 | 788 | |
paul@1458 | 789 | message = self.prepare_freebusy_message() |
paul@1458 | 790 | self.show_message(message, plain, filename) |
paul@1458 | 791 | |
paul@1457 | 792 | def show_cancel_publish_message(self, plain=False, filename=None): |
paul@1457 | 793 | |
paul@1457 | 794 | "Show the cancel message for the current user." |
paul@1457 | 795 | |
paul@1457 | 796 | message = self.prepare_cancel_publish_message() |
paul@1457 | 797 | self.show_message(message, plain, filename) |
paul@1457 | 798 | |
paul@1375 | 799 | def show_publish_message(self, plain=False, filename=None): |
paul@1375 | 800 | |
paul@1375 | 801 | "Show the publishing message for the updated event." |
paul@1375 | 802 | |
paul@1375 | 803 | message = self.prepare_publish_message() |
paul@1375 | 804 | self.show_message(message, plain, filename) |
paul@1375 | 805 | |
paul@1375 | 806 | def show_update_message(self, plain=False, filename=None): |
paul@1375 | 807 | |
paul@1375 | 808 | "Show the update message for the updated event." |
paul@1375 | 809 | |
paul@1375 | 810 | message = self.prepare_update_message() |
paul@1375 | 811 | if message: |
paul@1375 | 812 | self.show_message(message, plain, filename) |
paul@1375 | 813 | |
paul@1375 | 814 | # General display methods. |
paul@1375 | 815 | |
paul@1375 | 816 | def show_object(self): |
paul@1375 | 817 | print |
paul@1387 | 818 | print_title("Object details") |
paul@1387 | 819 | print |
paul@1375 | 820 | print "Summary:", self.state.get("summary") |
paul@1375 | 821 | print |
paul@1375 | 822 | print "Organiser:", self.state.get("organiser") |
paul@1375 | 823 | self.show_attendees() |
paul@1375 | 824 | self.show_periods() |
paul@1436 | 825 | self.show_rule() |
paul@1375 | 826 | self.show_suggested_attendees() |
paul@1375 | 827 | self.show_suggested_periods() |
paul@1375 | 828 | self.show_conflicting_periods() |
paul@1436 | 829 | print |
paul@1436 | 830 | print "Object is", self.obj.is_shared() and "shared" or "not shared" |
paul@1375 | 831 | |
paul@1375 | 832 | def show_attendees(self): |
paul@1375 | 833 | print |
paul@1375 | 834 | print_title("Attendees") |
paul@1375 | 835 | attendees = self.state.get("attendees") |
paul@1375 | 836 | for index, attendee_item in enumerate(attendees.items()): |
paul@1375 | 837 | show_attendee(attendee_item, index) |
paul@1375 | 838 | |
paul@1375 | 839 | def show_periods(self): |
paul@1375 | 840 | print |
paul@1375 | 841 | print_title("Periods") |
paul@1375 | 842 | show_periods(self.state.get("periods"), self.state.get("period_errors")) |
paul@1436 | 843 | |
paul@1436 | 844 | def show_rule(self): |
paul@1436 | 845 | selectors = self.state.get("rule") |
paul@1436 | 846 | if selectors: |
paul@1436 | 847 | print |
paul@1436 | 848 | print_title("Period recurrence rule") |
paul@1436 | 849 | show_rule(selectors) |
paul@1375 | 850 | |
paul@1375 | 851 | def show_suggested_attendees(self): |
paul@1375 | 852 | current_attendee = None |
paul@1375 | 853 | for index, (attendee, suggested_item) in enumerate(self.state.get("suggested_attendees")): |
paul@1375 | 854 | if attendee != current_attendee: |
paul@1375 | 855 | print |
paul@1375 | 856 | print_title("Attendees suggested by %s" % attendee) |
paul@1375 | 857 | current_attendee = attendee |
paul@1375 | 858 | show_attendee(suggested_item, index) |
paul@1375 | 859 | |
paul@1375 | 860 | def show_suggested_periods(self): |
paul@1375 | 861 | periods = self.state.get("suggested_periods") |
paul@1375 | 862 | current_attendee = None |
paul@1375 | 863 | index = 0 |
paul@1375 | 864 | for attendee, period, operation in periods: |
paul@1375 | 865 | if attendee != current_attendee: |
paul@1375 | 866 | print |
paul@1375 | 867 | print_title("Periods suggested by %s" % attendee) |
paul@1375 | 868 | current_attendee = attendee |
paul@1375 | 869 | show_period(period, index) |
paul@1375 | 870 | print " %s" % (operation == "add" and "Add this period" or "Remove this period") |
paul@1375 | 871 | index += 1 |
paul@1375 | 872 | |
paul@1375 | 873 | def show_conflicting_periods(self): |
paul@1375 | 874 | conflicts = self.get_conflicting_periods() |
paul@1375 | 875 | if not conflicts: |
paul@1375 | 876 | return |
paul@1375 | 877 | print |
paul@1375 | 878 | print_title("Conflicting periods") |
paul@1375 | 879 | |
paul@1375 | 880 | conflicts = list(conflicts) |
paul@1375 | 881 | conflicts.sort() |
paul@1375 | 882 | |
paul@1375 | 883 | for p in conflicts: |
paul@1375 | 884 | print p.summary, p.uid, p.get_start(), p.get_end() |
paul@1375 | 885 | |
paul@1375 | 886 | # Interaction functions. |
paul@1375 | 887 | |
paul@1375 | 888 | def expand_arg(args): |
paul@1436 | 889 | |
paul@1436 | 890 | """ |
paul@1436 | 891 | Expand the first argument in 'args' to a pair of arguments if having the |
paul@1436 | 892 | form <char><digit>... |
paul@1436 | 893 | """ |
paul@1436 | 894 | |
paul@1375 | 895 | if args[0] and args[0][1:].isdigit(): |
paul@1375 | 896 | args[:1] = [args[0][0], args[0][1:]] |
paul@1375 | 897 | |
paul@1436 | 898 | def get_text_arg(s): |
paul@1436 | 899 | |
paul@1436 | 900 | """ |
paul@1436 | 901 | Split 's' after the first whitespace occurrence, returning the remaining |
paul@1436 | 902 | text or None if no such text exists. |
paul@1436 | 903 | """ |
paul@1436 | 904 | |
paul@1436 | 905 | return (s.split(None, 1)[1:] or [None])[0] |
paul@1375 | 906 | |
paul@1375 | 907 | def next_arg(args): |
paul@1436 | 908 | |
paul@1436 | 909 | """ |
paul@1436 | 910 | Return the first argument from 'args', removing it, or return None if no |
paul@1436 | 911 | arguments are left. |
paul@1436 | 912 | """ |
paul@1436 | 913 | |
paul@1375 | 914 | if args: |
paul@1375 | 915 | arg = args[0] |
paul@1375 | 916 | del args[0] |
paul@1375 | 917 | return arg |
paul@1375 | 918 | return None |
paul@1375 | 919 | |
paul@1436 | 920 | # Editing functions. |
paul@1436 | 921 | |
paul@1375 | 922 | def edit_period(period, args=None): |
paul@1375 | 923 | |
paul@1375 | 924 | "Edit the given 'period'." |
paul@1375 | 925 | |
paul@1375 | 926 | print "Editing start (%s)" % period.get_start() |
paul@1375 | 927 | edit_date(period.start, args) |
paul@1375 | 928 | print "Editing end (%s)" % period.get_end() |
paul@1375 | 929 | edit_date(period.end, args) |
paul@1375 | 930 | |
paul@1453 | 931 | period.reset() |
paul@1453 | 932 | |
paul@1375 | 933 | def edit_date(date, args=None): |
paul@1375 | 934 | |
paul@1375 | 935 | "Edit the given 'date' object attributes." |
paul@1375 | 936 | |
paul@1375 | 937 | date.date = next_arg(args) or input_with_default("Date (%s)? ", date.date) |
paul@1375 | 938 | date.hour = next_arg(args) or input_with_default("Hour (%s)? ", date.hour) |
paul@1453 | 939 | |
paul@1453 | 940 | # Permit day-level datetimes. |
paul@1453 | 941 | |
paul@1453 | 942 | if date.hour == "-": |
paul@1453 | 943 | date.set_as_day() |
paul@1453 | 944 | else: |
paul@1453 | 945 | date.minute = next_arg(args) or input_with_default("Minute (%s)? ", date.minute) |
paul@1453 | 946 | date.second = next_arg(args) or input_with_default("Second (%s)? ", date.second) |
paul@1453 | 947 | date.tzid = next_arg(args) or input_with_default("Time zone (%s)? ", date.tzid) |
paul@1453 | 948 | |
paul@1375 | 949 | date.reset() |
paul@1375 | 950 | |
paul@1436 | 951 | def add_rule_selector_count(selectors, args, selector=None): |
paul@1436 | 952 | |
paul@1436 | 953 | "Add to 'selectors' a selector imposing a count restriction." |
paul@1436 | 954 | |
paul@1436 | 955 | while True: |
paul@1436 | 956 | arg = next_arg(args) |
paul@1436 | 957 | if not arg: |
paul@1436 | 958 | if selector: |
paul@1436 | 959 | arg = input_with_default("Number of occurrences (%d)? ", |
paul@1436 | 960 | selector.get_limit()) |
paul@1436 | 961 | else: |
paul@1436 | 962 | arg = read_input("Number of occurrences? ") |
paul@1436 | 963 | |
paul@1436 | 964 | count = to_int_or_none(arg) |
paul@1436 | 965 | |
paul@1436 | 966 | if count is None: |
paul@1436 | 967 | arg = None |
paul@1436 | 968 | continue |
paul@1436 | 969 | |
paul@1436 | 970 | # Change or add selector. |
paul@1436 | 971 | |
paul@1436 | 972 | selector = selector or selectors and \ |
paul@1436 | 973 | isinstance(selectors[0], vRecurrence.LimitSelector) and \ |
paul@1436 | 974 | selectors[0] or None |
paul@1436 | 975 | |
paul@1436 | 976 | if not selector: |
paul@1436 | 977 | selector = vRecurrence.new_selector("COUNT") |
paul@1436 | 978 | selectors.insert(0, selector) |
paul@1436 | 979 | |
paul@1436 | 980 | selector.set_limit(count) |
paul@1436 | 981 | break |
paul@1436 | 982 | |
paul@1436 | 983 | def add_rule_selector_frequency(selectors, args, selector=None): |
paul@1436 | 984 | |
paul@1436 | 985 | "Add to 'selectors' a selector for a frequency." |
paul@1436 | 986 | |
paul@1436 | 987 | while not selector: |
paul@1436 | 988 | arg = next_arg(args) |
paul@1436 | 989 | if not arg: |
paul@1436 | 990 | arg = read_input("Select (y)early, (M)onthly, (w)eekly, (d)aily, " |
paul@1436 | 991 | "(h)ourly, (m)inutely, (s)econdly (or return)? ") |
paul@1436 | 992 | |
paul@1436 | 993 | if not arg: |
paul@1436 | 994 | return |
paul@1436 | 995 | |
paul@1436 | 996 | arg_lower = arg.lower() |
paul@1436 | 997 | |
paul@1442 | 998 | if arg_lower in YEARLY_VALUES: |
paul@1436 | 999 | qualifier = "YEARLY" |
paul@1442 | 1000 | elif arg == "M" or arg_lower in MONTHLY_VALUES: |
paul@1436 | 1001 | qualifier = "MONTHLY" |
paul@1442 | 1002 | elif arg_lower in WEEKLY_VALUES: |
paul@1436 | 1003 | qualifier = "WEEKLY" |
paul@1442 | 1004 | elif arg_lower in DAILY_VALUES: |
paul@1436 | 1005 | qualifier = "DAILY" |
paul@1442 | 1006 | elif arg_lower in HOURLY_VALUES: |
paul@1436 | 1007 | qualifier = "HOURLY" |
paul@1442 | 1008 | elif arg == "m" or arg_lower in MINUTELY_VALUES: |
paul@1436 | 1009 | qualifier = "MINUTELY" |
paul@1442 | 1010 | elif arg_lower in SECONDLY_VALUES: |
paul@1436 | 1011 | qualifier = "SECONDLY" |
paul@1436 | 1012 | else: |
paul@1436 | 1013 | continue |
paul@1436 | 1014 | |
paul@1436 | 1015 | break |
paul@1436 | 1016 | |
paul@1436 | 1017 | while True: |
paul@1436 | 1018 | arg = next_arg(args) |
paul@1436 | 1019 | if not arg: |
paul@1436 | 1020 | if selector: |
paul@1436 | 1021 | arg = input_with_default("Interval (%d)? ", |
paul@1436 | 1022 | selector.get_interval()) |
paul@1436 | 1023 | else: |
paul@1436 | 1024 | arg = input_with_default("Interval (%d)? ", 1) |
paul@1436 | 1025 | |
paul@1436 | 1026 | interval = to_int_or_none(arg) |
paul@1436 | 1027 | |
paul@1436 | 1028 | if interval is None: |
paul@1436 | 1029 | arg = None |
paul@1436 | 1030 | else: |
paul@1436 | 1031 | break |
paul@1436 | 1032 | |
paul@1436 | 1033 | # Update an existing selector. |
paul@1436 | 1034 | |
paul@1436 | 1035 | if selector: |
paul@1436 | 1036 | selector.set_interval(interval) |
paul@1436 | 1037 | return |
paul@1436 | 1038 | |
paul@1436 | 1039 | # Create a new selector. |
paul@1436 | 1040 | |
paul@1436 | 1041 | selector = vRecurrence.new_selector(qualifier) |
paul@1436 | 1042 | selector.set_interval(interval) |
paul@1436 | 1043 | |
paul@1436 | 1044 | # Remove any existing frequency selector. |
paul@1436 | 1045 | |
paul@1436 | 1046 | for index, _selector in enumerate(selectors): |
paul@1436 | 1047 | if isinstance(_selector, vRecurrence.Pattern): |
paul@1436 | 1048 | del selectors[index] |
paul@1436 | 1049 | break |
paul@1436 | 1050 | |
paul@1436 | 1051 | # Add the new selector and keep the selectors in order. |
paul@1436 | 1052 | |
paul@1436 | 1053 | selectors.append(selector) |
paul@1436 | 1054 | vRecurrence.sort_selectors(selectors) |
paul@1436 | 1055 | |
paul@1436 | 1056 | def add_rule_selector_selection(selectors, args, selector=None): |
paul@1436 | 1057 | |
paul@1436 | 1058 | "Add to 'selectors' a selector for a particular point in time." |
paul@1436 | 1059 | |
paul@1436 | 1060 | qualifier = selector and selector.qualifier or None |
paul@1436 | 1061 | |
paul@1436 | 1062 | while not selector: |
paul@1436 | 1063 | arg = next_arg(args) |
paul@1436 | 1064 | if not arg: |
paul@1436 | 1065 | arg = read_input("Select (M)onths, (w)eeks, (y)eardays, " |
paul@1436 | 1066 | "m(o)nthdays, week(d)ays, (h)ours, (m)inutes, " |
paul@1436 | 1067 | "(s)econds (or return)? ") |
paul@1436 | 1068 | |
paul@1436 | 1069 | if not arg: |
paul@1436 | 1070 | return |
paul@1436 | 1071 | |
paul@1436 | 1072 | arg_lower = arg.lower() |
paul@1436 | 1073 | |
paul@1442 | 1074 | if arg == "M" or arg_lower in MONTH_VALUES: |
paul@1436 | 1075 | qualifier = "BYMONTH" |
paul@1442 | 1076 | elif arg_lower in WEEK_VALUES: |
paul@1436 | 1077 | qualifier = "BYWEEKNO" |
paul@1442 | 1078 | elif arg_lower in YEARDAY_VALUES: |
paul@1436 | 1079 | qualifier = "BYYEARDAY" |
paul@1442 | 1080 | elif arg_lower in MONTHDAY_VALUES: |
paul@1436 | 1081 | qualifier = "BYMONTHDAY" |
paul@1442 | 1082 | elif arg_lower in DAY_VALUES: |
paul@1436 | 1083 | qualifier = "BYDAY" |
paul@1442 | 1084 | elif arg_lower in HOUR_VALUES: |
paul@1436 | 1085 | qualifier = "BYHOUR" |
paul@1442 | 1086 | elif arg == "m" or arg_lower in MINUTE_VALUES: |
paul@1436 | 1087 | qualifier = "BYMINUTE" |
paul@1442 | 1088 | elif arg_lower in SECOND_VALUES: |
paul@1436 | 1089 | qualifier = "BYSECOND" |
paul@1436 | 1090 | else: |
paul@1436 | 1091 | continue |
paul@1436 | 1092 | |
paul@1436 | 1093 | break |
paul@1436 | 1094 | |
paul@1436 | 1095 | if not qualifier: |
paul@1436 | 1096 | return |
paul@1436 | 1097 | |
paul@1436 | 1098 | ranges = vRecurrence.get_value_ranges(qualifier) |
paul@1436 | 1099 | ranges_str = format_value_ranges(ranges[0]) |
paul@1436 | 1100 | |
paul@1436 | 1101 | values = [] |
paul@1436 | 1102 | |
paul@1436 | 1103 | while True: |
paul@1436 | 1104 | arg = next_arg(args) |
paul@1436 | 1105 | if not arg: |
paul@1436 | 1106 | arg = read_input("Value (%s) (return to end)? " % ranges_str) |
paul@1436 | 1107 | |
paul@1436 | 1108 | # Stop if no more arguments. |
paul@1436 | 1109 | |
paul@1436 | 1110 | if not arg or arg == "end": |
paul@1436 | 1111 | break |
paul@1436 | 1112 | |
paul@1436 | 1113 | # Handle weekdays. |
paul@1436 | 1114 | |
paul@1436 | 1115 | if qualifier == "BYDAY": |
paul@1436 | 1116 | value = arg.upper() # help to match weekdays |
paul@1436 | 1117 | |
paul@1436 | 1118 | arg = next_arg(args) |
paul@1436 | 1119 | if not arg: |
paul@1436 | 1120 | arg = read_input("Occurrence within a month? ") |
paul@1436 | 1121 | |
paul@1436 | 1122 | index = to_int_or_none(arg) |
paul@1436 | 1123 | value = vRecurrence.check_values(qualifier, [value, index]) |
paul@1436 | 1124 | |
paul@1436 | 1125 | # Handle all other values. |
paul@1436 | 1126 | |
paul@1436 | 1127 | else: |
paul@1436 | 1128 | value = to_int_or_none(arg) |
paul@1436 | 1129 | l = vRecurrence.check_values(qualifier, [value]) |
paul@1436 | 1130 | value = l and l[0] |
paul@1436 | 1131 | |
paul@1436 | 1132 | # Append valid values. |
paul@1436 | 1133 | |
paul@1436 | 1134 | if value is not None: |
paul@1436 | 1135 | values.append(value) |
paul@1436 | 1136 | else: |
paul@1436 | 1137 | print "Value not recognised." |
paul@1436 | 1138 | |
paul@1436 | 1139 | if not values: |
paul@1436 | 1140 | return |
paul@1436 | 1141 | |
paul@1436 | 1142 | # Update an existing selector. |
paul@1436 | 1143 | |
paul@1436 | 1144 | if selector: |
paul@1436 | 1145 | selector.set_values(values) |
paul@1436 | 1146 | return |
paul@1436 | 1147 | |
paul@1436 | 1148 | # Create a new selector. |
paul@1436 | 1149 | |
paul@1436 | 1150 | selector = vRecurrence.new_selector(qualifier) |
paul@1436 | 1151 | selector.set_values(values) |
paul@1436 | 1152 | |
paul@1436 | 1153 | # Remove any existing selector. |
paul@1436 | 1154 | |
paul@1436 | 1155 | for index, _selector in enumerate(selectors): |
paul@1436 | 1156 | if _selector.qualifier == selector.qualifier: |
paul@1436 | 1157 | del selectors[index] |
paul@1436 | 1158 | break |
paul@1436 | 1159 | |
paul@1436 | 1160 | # Add the new selector and keep the selectors in order. |
paul@1436 | 1161 | |
paul@1436 | 1162 | selectors.append(selector) |
paul@1436 | 1163 | vRecurrence.sort_selectors(selectors) |
paul@1436 | 1164 | |
paul@1375 | 1165 | def select_object(cl, objects): |
paul@1436 | 1166 | |
paul@1436 | 1167 | "Select using 'cl' an object from the given 'objects'." |
paul@1436 | 1168 | |
paul@1375 | 1169 | print |
paul@1387 | 1170 | |
paul@1387 | 1171 | if objects: |
paul@1387 | 1172 | label = "Select object number or (n)ew object or (q)uit> " |
paul@1387 | 1173 | else: |
paul@1387 | 1174 | label = "Select (n)ew object or (q)uit> " |
paul@1387 | 1175 | |
paul@1375 | 1176 | while True: |
paul@1375 | 1177 | try: |
paul@1387 | 1178 | cmd = read_input(label) |
paul@1375 | 1179 | except EOFError: |
paul@1375 | 1180 | return None |
paul@1375 | 1181 | |
paul@1375 | 1182 | if cmd.isdigit(): |
paul@1436 | 1183 | index = to_int_or_none(cmd) |
paul@1436 | 1184 | |
paul@1436 | 1185 | if index is not None and 0 <= index < len(objects): |
paul@1375 | 1186 | obj = objects[index] |
paul@1375 | 1187 | return cl.load_object(obj.get_uid(), obj.get_recurrenceid()) |
paul@1387 | 1188 | |
paul@1442 | 1189 | elif cmd in NEW_COMMANDS: |
paul@1375 | 1190 | return cl.new_object() |
paul@1442 | 1191 | elif cmd in QUIT_COMMANDS: |
paul@1375 | 1192 | return None |
paul@1375 | 1193 | |
paul@1387 | 1194 | def show_commands(): |
paul@1436 | 1195 | |
paul@1436 | 1196 | "Show editing and inspection commands." |
paul@1436 | 1197 | |
paul@1375 | 1198 | print |
paul@1375 | 1199 | print_title("Editing commands") |
paul@1375 | 1200 | print |
paul@1375 | 1201 | print """\ |
paul@1439 | 1202 | %(ATTENDEE_NEW_COMMANDS)s |
paul@1375 | 1203 | Add attendee |
paul@1375 | 1204 | |
paul@1439 | 1205 | %(ATTENDEE_COMMANDS)s |
paul@1375 | 1206 | Select attendee from list |
paul@1375 | 1207 | |
paul@1439 | 1208 | %(ATTENDANCE_COMMANDS)s |
paul@1439 | 1209 | Change attendance/participation |
paul@1439 | 1210 | |
paul@1439 | 1211 | %(SUGGESTED_ATTENDEE_COMMANDS)s |
paul@1375 | 1212 | Add suggested attendee from list |
paul@1375 | 1213 | |
paul@1439 | 1214 | %(FINISH_COMMANDS)s |
paul@1375 | 1215 | Finish editing, confirming changes, proceeding to messaging |
paul@1375 | 1216 | |
paul@1439 | 1217 | %(HELP_COMMANDS)s |
paul@1375 | 1218 | Show this help message |
paul@1375 | 1219 | |
paul@1439 | 1220 | %(LIST_COMMANDS)s |
paul@1375 | 1221 | List/show all event details |
paul@1375 | 1222 | |
paul@1439 | 1223 | %(PERIOD_NEW_COMMANDS)s |
paul@1375 | 1224 | Add new period |
paul@1375 | 1225 | |
paul@1439 | 1226 | %(PERIOD_COMMANDS)s |
paul@1375 | 1227 | Select period from list |
paul@1375 | 1228 | |
paul@1439 | 1229 | %(SUGGESTED_PERIOD_COMMANDS)s |
paul@1375 | 1230 | Add or remove suggested period from list |
paul@1375 | 1231 | |
paul@1439 | 1232 | %(QUIT_COMMANDS)s |
paul@1375 | 1233 | Exit/quit this program |
paul@1375 | 1234 | |
paul@1439 | 1235 | %(RESET_COMMANDS)s |
paul@1375 | 1236 | Reset event periods (return to editing mode, if already finished) |
paul@1375 | 1237 | |
paul@1439 | 1238 | %(RULE_NEW_COMMANDS)s |
paul@1436 | 1239 | Add a period recurrence rule |
paul@1436 | 1240 | |
paul@1439 | 1241 | %(RULE_COMMANDS)s |
paul@1436 | 1242 | Select period recurrence rule selector from list |
paul@1396 | 1243 | |
paul@1439 | 1244 | %(SUMMARY_COMMANDS)s |
paul@1375 | 1245 | Set event summary |
paul@1439 | 1246 | """ % { |
paul@1439 | 1247 | "ATTENDEE_NEW_COMMANDS" : commandlist(ATTENDEE_COMMANDS, "[ <uri> ]"), |
paul@1439 | 1248 | "ATTENDEE_COMMANDS" : commandlist(ATTENDEE_COMMANDS, "<number>"), |
paul@1439 | 1249 | "ATTENDANCE_COMMANDS" : commandlist(ATTENDANCE_COMMANDS), |
paul@1439 | 1250 | "SUGGESTED_ATTENDEE_COMMANDS" : commandlist(SUGGESTED_ATTENDEE_COMMANDS, "<number>"), |
paul@1439 | 1251 | "FINISH_COMMANDS" : commandlist(FINISH_COMMANDS), |
paul@1439 | 1252 | "HELP_COMMANDS" : commandlist(HELP_COMMANDS), |
paul@1439 | 1253 | "LIST_COMMANDS" : commandlist(LIST_COMMANDS), |
paul@1439 | 1254 | "PERIOD_NEW_COMMANDS" : commandlist(PERIOD_COMMANDS, "[ new ]"), |
paul@1439 | 1255 | "PERIOD_COMMANDS" : commandlist(PERIOD_COMMANDS, "<number>"), |
paul@1439 | 1256 | "SUGGESTED_PERIOD_COMMANDS" : commandlist(SUGGESTED_PERIOD_COMMANDS, "<number>"), |
paul@1439 | 1257 | "QUIT_COMMANDS" : commandlist(QUIT_COMMANDS), |
paul@1439 | 1258 | "RESET_COMMANDS" : commandlist(RESET_COMMANDS), |
paul@1439 | 1259 | "RULE_NEW_COMMANDS" : commandlist(RULE_COMMANDS), |
paul@1439 | 1260 | "RULE_COMMANDS" : commandlist(RULE_COMMANDS, "<number>"), |
paul@1439 | 1261 | "SUMMARY_COMMANDS" : commandlist(SUMMARY_COMMANDS), |
paul@1439 | 1262 | } |
paul@1375 | 1263 | |
paul@1387 | 1264 | print_title("Messaging commands") |
paul@1387 | 1265 | print |
paul@1387 | 1266 | print """\ |
paul@1387 | 1267 | S, send |
paul@1387 | 1268 | Send messages to recipients and to self, if appropriate |
paul@1387 | 1269 | """ |
paul@1387 | 1270 | |
paul@1375 | 1271 | print_title("Diagnostic commands") |
paul@1375 | 1272 | print |
paul@1375 | 1273 | print """\ |
paul@1375 | 1274 | c, class, classification |
paul@1375 | 1275 | Show period classification |
paul@1375 | 1276 | |
paul@1375 | 1277 | C, changes |
paul@1375 | 1278 | Show changes made by editing |
paul@1375 | 1279 | |
paul@1375 | 1280 | o, ops, operations |
paul@1375 | 1281 | Show update operations |
paul@1375 | 1282 | |
paul@1375 | 1283 | RECURRENCE-ID [ <filename> ] |
paul@1375 | 1284 | Show event recurrence identifier, writing to <filename> if specified |
paul@1375 | 1285 | |
paul@1375 | 1286 | UID [ <filename> ] |
paul@1375 | 1287 | Show event unique identifier, writing to <filename> if specified |
paul@1375 | 1288 | """ |
paul@1375 | 1289 | |
paul@1387 | 1290 | print_title("Message inspection commands") |
paul@1375 | 1291 | print |
paul@1375 | 1292 | print """\ |
paul@1458 | 1293 | %(FREEBUSY_COMMANDS)s |
paul@1458 | 1294 | Show free/busy request message, writing to <filename> if specified |
paul@1458 | 1295 | |
paul@1458 | 1296 | %(PUBLISH_COMMANDS)s |
paul@1375 | 1297 | publish [ <filename> ] |
paul@1375 | 1298 | Show publishing message, writing to <filename> if specified |
paul@1375 | 1299 | |
paul@1458 | 1300 | %(CANCEL_COMMANDS)s |
paul@1375 | 1301 | Show cancellation message sent to uninvited/removed recipients, writing to |
paul@1375 | 1302 | <filename> if specified |
paul@1375 | 1303 | |
paul@1458 | 1304 | %(CANCEL_PUBLISH_COMMANDS)s |
paul@1457 | 1305 | Show cancellation message for use by the organiser, writing to <filename> if |
paul@1457 | 1306 | specified |
paul@1457 | 1307 | |
paul@1458 | 1308 | %(UPDATE_COMMANDS)s |
paul@1375 | 1309 | Show update message, writing to <filename> if specified |
paul@1458 | 1310 | """ % { |
paul@1458 | 1311 | "CANCEL_COMMANDS" : commandlist(CANCEL_COMMANDS, "[ <filename> ]"), |
paul@1458 | 1312 | "CANCEL_PUBLISH_COMMANDS" : commandlist(CANCEL_PUBLISH_COMMANDS, "[ <filename> ]"), |
paul@1458 | 1313 | "FREEBUSY_COMMANDS" : commandlist(FREEBUSY_COMMANDS, "[ <filename> ]"), |
paul@1458 | 1314 | "PUBLISH_COMMANDS" : commandlist(PUBLISH_COMMANDS, "[ <filename> ]"), |
paul@1458 | 1315 | "UPDATE_COMMANDS" : commandlist(UPDATE_COMMANDS, "[ <filename> ]"), |
paul@1458 | 1316 | } |
paul@1375 | 1317 | |
paul@1387 | 1318 | def edit_object(cl, obj, handle_outgoing=False): |
paul@1436 | 1319 | |
paul@1436 | 1320 | """ |
paul@1436 | 1321 | Edit using 'cl' the given object 'obj'. If 'handle_outgoing' is specified |
paul@1436 | 1322 | and set to a true value, the details from outgoing messages are incorporated |
paul@1436 | 1323 | into the stored data. |
paul@1436 | 1324 | """ |
paul@1436 | 1325 | |
paul@1375 | 1326 | cl.show_object() |
paul@1375 | 1327 | print |
paul@1375 | 1328 | |
paul@1375 | 1329 | try: |
paul@1375 | 1330 | while True: |
paul@1375 | 1331 | role = cl.is_organiser() and "Organiser" or "Attendee" |
paul@1375 | 1332 | status = cl.state.get("finished") and " (editing complete)" or "" |
paul@1375 | 1333 | |
paul@1436 | 1334 | s = read_input("%s%s> " % (role, status)) |
paul@1436 | 1335 | args = s.split() |
paul@1375 | 1336 | |
paul@1375 | 1337 | if not args or not args[0]: |
paul@1375 | 1338 | continue |
paul@1375 | 1339 | |
paul@1436 | 1340 | # Expand short-form arguments. |
paul@1436 | 1341 | |
paul@1436 | 1342 | expand_arg(args) |
paul@1436 | 1343 | cmd = next_arg(args) |
paul@1436 | 1344 | |
paul@1375 | 1345 | # Check the status of the periods. |
paul@1375 | 1346 | |
paul@1439 | 1347 | if cmd in CLASSIFICATION_COMMANDS: |
paul@1375 | 1348 | cl.show_period_classification() |
paul@1375 | 1349 | print |
paul@1375 | 1350 | |
paul@1439 | 1351 | elif cmd in CHANGE_COMMANDS: |
paul@1375 | 1352 | cl.show_changes() |
paul@1375 | 1353 | print |
paul@1375 | 1354 | |
paul@1375 | 1355 | # Finish editing. |
paul@1375 | 1356 | |
paul@1439 | 1357 | elif cmd in FINISH_COMMANDS: |
paul@1375 | 1358 | cl.finish() |
paul@1375 | 1359 | |
paul@1375 | 1360 | # Help. |
paul@1375 | 1361 | |
paul@1439 | 1362 | elif cmd in HELP_COMMANDS: |
paul@1387 | 1363 | show_commands() |
paul@1375 | 1364 | |
paul@1375 | 1365 | # Show object details. |
paul@1375 | 1366 | |
paul@1439 | 1367 | elif cmd in LIST_COMMANDS: |
paul@1375 | 1368 | cl.show_object() |
paul@1375 | 1369 | print |
paul@1375 | 1370 | |
paul@1375 | 1371 | # Show the operations. |
paul@1375 | 1372 | |
paul@1439 | 1373 | elif cmd in OPERATION_COMMANDS: |
paul@1375 | 1374 | cl.show_operations() |
paul@1375 | 1375 | print |
paul@1375 | 1376 | |
paul@1375 | 1377 | # Quit or exit. |
paul@1375 | 1378 | |
paul@1439 | 1379 | elif cmd in QUIT_COMMANDS: |
paul@1375 | 1380 | break |
paul@1375 | 1381 | |
paul@1375 | 1382 | # Restart editing. |
paul@1375 | 1383 | |
paul@1439 | 1384 | elif cmd in RESET_COMMANDS: |
paul@1375 | 1385 | obj = cl.load_object(obj.get_uid(), obj.get_recurrenceid()) |
paul@1375 | 1386 | if not obj: |
paul@1375 | 1387 | obj = cl.new_object() |
paul@1375 | 1388 | cl.reset() |
paul@1375 | 1389 | cl.show_object() |
paul@1375 | 1390 | print |
paul@1375 | 1391 | |
paul@1375 | 1392 | # Show UID details. |
paul@1375 | 1393 | |
paul@1439 | 1394 | elif cmd in UID_COMMANDS: |
paul@1436 | 1395 | filename = get_text_arg(s) |
paul@1375 | 1396 | write(obj.get_uid(), filename) |
paul@1375 | 1397 | |
paul@1439 | 1398 | elif cmd in RECURRENCEID_COMMANDS: |
paul@1436 | 1399 | filename = get_text_arg(s) |
paul@1375 | 1400 | write(obj.get_recurrenceid() or "", filename) |
paul@1375 | 1401 | |
paul@1375 | 1402 | # Post-editing operations. |
paul@1375 | 1403 | |
paul@1375 | 1404 | elif cl.state.get("finished"): |
paul@1375 | 1405 | |
paul@1375 | 1406 | # Show messages. |
paul@1375 | 1407 | |
paul@1439 | 1408 | if cmd in PUBLISH_COMMANDS: |
paul@1436 | 1409 | filename = get_text_arg(s) |
paul@1375 | 1410 | cl.show_publish_message(plain=not filename, filename=filename) |
paul@1375 | 1411 | |
paul@1457 | 1412 | elif cmd in CANCEL_PUBLISH_COMMANDS: |
paul@1457 | 1413 | filename = get_text_arg(s) |
paul@1457 | 1414 | cl.show_cancel_publish_message(plain=not filename, filename=filename) |
paul@1457 | 1415 | |
paul@1439 | 1416 | elif cmd in CANCEL_COMMANDS: |
paul@1436 | 1417 | filename = get_text_arg(s) |
paul@1375 | 1418 | cl.show_cancel_message(plain=not filename, filename=filename) |
paul@1375 | 1419 | |
paul@1439 | 1420 | elif cmd in UPDATE_COMMANDS: |
paul@1436 | 1421 | filename = get_text_arg(s) |
paul@1375 | 1422 | cl.show_update_message(plain=not filename, filename=filename) |
paul@1375 | 1423 | |
paul@1458 | 1424 | elif cmd in FREEBUSY_COMMANDS: |
paul@1458 | 1425 | filename = get_text_arg(s) |
paul@1458 | 1426 | cl.show_freebusy_message(plain=not filename, filename=filename) |
paul@1458 | 1427 | |
paul@1387 | 1428 | # Definitive finishing action. |
paul@1387 | 1429 | |
paul@1439 | 1430 | elif cmd in SEND_COMMANDS: |
paul@1387 | 1431 | |
paul@1387 | 1432 | # Send update and cancellation messages. |
paul@1387 | 1433 | |
paul@1387 | 1434 | did_send = False |
paul@1387 | 1435 | |
paul@1387 | 1436 | message = cl.prepare_update_message() |
paul@1387 | 1437 | if message: |
paul@1387 | 1438 | cl.send_message(message, cl.get_recipients()) |
paul@1387 | 1439 | did_send = True |
paul@1387 | 1440 | |
paul@1387 | 1441 | to_cancel = cl.state.get("attendees_to_cancel") |
paul@1387 | 1442 | if to_cancel: |
paul@1387 | 1443 | message = cl.prepare_cancel_message() |
paul@1387 | 1444 | if message: |
paul@1387 | 1445 | cl.send_message(message, to_cancel) |
paul@1387 | 1446 | did_send = True |
paul@1387 | 1447 | |
paul@1387 | 1448 | # Process the object using the person outgoing handler. |
paul@1387 | 1449 | |
paul@1387 | 1450 | if handle_outgoing: |
paul@1436 | 1451 | cl.handle_outgoing_object() |
paul@1387 | 1452 | |
paul@1387 | 1453 | # Otherwise, send a message to self with the event details. |
paul@1387 | 1454 | |
paul@1387 | 1455 | else: |
paul@1387 | 1456 | message = cl.prepare_publish_message() |
paul@1387 | 1457 | if message: |
paul@1387 | 1458 | cl.send_message_to_self(message) |
paul@1387 | 1459 | did_send = True |
paul@1387 | 1460 | |
paul@1387 | 1461 | # Exit if sending occurred. |
paul@1387 | 1462 | |
paul@1387 | 1463 | if did_send: |
paul@1387 | 1464 | break |
paul@1387 | 1465 | else: |
paul@1387 | 1466 | print "No messages sent. Try making edits or exit manually." |
paul@1387 | 1467 | |
paul@1375 | 1468 | # Editing operations. |
paul@1375 | 1469 | |
paul@1375 | 1470 | elif not cl.state.get("finished"): |
paul@1375 | 1471 | |
paul@1375 | 1472 | # Add or edit attendee. |
paul@1375 | 1473 | |
paul@1439 | 1474 | if cmd in ATTENDEE_COMMANDS: |
paul@1375 | 1475 | value = next_arg(args) |
paul@1436 | 1476 | index = to_int_or_none(value) |
paul@1375 | 1477 | |
paul@1436 | 1478 | if index is None: |
paul@1375 | 1479 | try: |
paul@1375 | 1480 | index = cl.find_attendee(value) |
paul@1375 | 1481 | except ValueError: |
paul@1375 | 1482 | index = None |
paul@1375 | 1483 | |
paul@1375 | 1484 | # Add an attendee. |
paul@1375 | 1485 | |
paul@1375 | 1486 | if index is None: |
paul@1375 | 1487 | cl.add_attendee(value) |
paul@1375 | 1488 | if not value: |
paul@1375 | 1489 | cl.edit_attendee(-1) |
paul@1375 | 1490 | |
paul@1375 | 1491 | # Edit attendee (using index). |
paul@1375 | 1492 | |
paul@1375 | 1493 | else: |
paul@1375 | 1494 | attendee_item = cl.can_remove_attendee(index) |
paul@1375 | 1495 | if attendee_item: |
paul@1375 | 1496 | while True: |
paul@1375 | 1497 | show_attendee(attendee_item, index) |
paul@1375 | 1498 | |
paul@1375 | 1499 | # Obtain a command from any arguments. |
paul@1375 | 1500 | |
paul@1375 | 1501 | cmd = next_arg(args) |
paul@1375 | 1502 | if not cmd: |
paul@1436 | 1503 | cmd = read_input("Attendee: (e)dit, (r)emove (or return)> ") |
paul@1439 | 1504 | if cmd in EDIT_COMMANDS: |
paul@1375 | 1505 | cl.edit_attendee(index) |
paul@1439 | 1506 | elif cmd in REMOVE_COMMANDS: |
paul@1375 | 1507 | cl.remove_attendees([index]) |
paul@1436 | 1508 | |
paul@1436 | 1509 | # Exit if requested or after a successful |
paul@1436 | 1510 | # operation. |
paul@1436 | 1511 | |
paul@1375 | 1512 | elif not cmd: |
paul@1375 | 1513 | pass |
paul@1375 | 1514 | else: |
paul@1375 | 1515 | continue |
paul@1375 | 1516 | break |
paul@1375 | 1517 | |
paul@1375 | 1518 | cl.show_attendees() |
paul@1375 | 1519 | print |
paul@1375 | 1520 | |
paul@1375 | 1521 | # Add suggested attendee (using index). |
paul@1375 | 1522 | |
paul@1439 | 1523 | elif cmd in SUGGESTED_ATTENDEE_COMMANDS: |
paul@1436 | 1524 | value = next_arg(args) |
paul@1436 | 1525 | index = to_int_or_none(value) |
paul@1436 | 1526 | |
paul@1436 | 1527 | if index is not None: |
paul@1375 | 1528 | cl.add_suggested_attendee(index) |
paul@1436 | 1529 | |
paul@1375 | 1530 | cl.show_attendees() |
paul@1375 | 1531 | print |
paul@1375 | 1532 | |
paul@1375 | 1533 | # Edit attendance. |
paul@1375 | 1534 | |
paul@1439 | 1535 | elif cmd in ATTENDANCE_COMMANDS: |
paul@1375 | 1536 | |
paul@1375 | 1537 | if not cl.is_attendee() and cl.is_organiser(): |
paul@1375 | 1538 | cl.add_attendee(cl.user) |
paul@1375 | 1539 | |
paul@1375 | 1540 | # NOTE: Support delegation. |
paul@1375 | 1541 | |
paul@1375 | 1542 | if cl.can_edit_attendance(): |
paul@1375 | 1543 | while True: |
paul@1375 | 1544 | |
paul@1375 | 1545 | # Obtain a command from any arguments. |
paul@1375 | 1546 | |
paul@1375 | 1547 | cmd = next_arg(args) |
paul@1375 | 1548 | if not cmd: |
paul@1436 | 1549 | cmd = read_input("Attendance: (a)ccept, (d)ecline, (t)entative (or return)> ") |
paul@1439 | 1550 | if cmd in ACCEPTED_VALUES: |
paul@1375 | 1551 | cl.edit_attendance("ACCEPTED") |
paul@1439 | 1552 | elif cmd in DECLINED_VALUES: |
paul@1375 | 1553 | cl.edit_attendance("DECLINED") |
paul@1439 | 1554 | elif cmd in TENTATIVE_VALUES: |
paul@1375 | 1555 | cl.edit_attendance("TENTATIVE") |
paul@1436 | 1556 | |
paul@1436 | 1557 | # Exit if requested or after a successful operation. |
paul@1436 | 1558 | |
paul@1375 | 1559 | elif not cmd: |
paul@1375 | 1560 | pass |
paul@1375 | 1561 | else: |
paul@1375 | 1562 | continue |
paul@1375 | 1563 | break |
paul@1375 | 1564 | |
paul@1375 | 1565 | cl.show_attendees() |
paul@1375 | 1566 | print |
paul@1375 | 1567 | |
paul@1375 | 1568 | # Add or edit period. |
paul@1375 | 1569 | |
paul@1439 | 1570 | elif cmd in PERIOD_COMMANDS: |
paul@1375 | 1571 | value = next_arg(args) |
paul@1436 | 1572 | index = to_int_or_none(value) |
paul@1375 | 1573 | |
paul@1375 | 1574 | # Add a new period. |
paul@1375 | 1575 | |
paul@1436 | 1576 | if index is None or value == "new": |
paul@1375 | 1577 | cl.add_period() |
paul@1436 | 1578 | cl.edit_period(-1, args) |
paul@1375 | 1579 | |
paul@1375 | 1580 | # Edit period (using index). |
paul@1375 | 1581 | |
paul@1375 | 1582 | else: |
paul@1375 | 1583 | period = cl.can_edit_period(index) |
paul@1375 | 1584 | if period: |
paul@1375 | 1585 | while True: |
paul@1375 | 1586 | show_period_raw(period) |
paul@1375 | 1587 | |
paul@1375 | 1588 | # Obtain a command from any arguments. |
paul@1375 | 1589 | |
paul@1375 | 1590 | cmd = next_arg(args) |
paul@1375 | 1591 | if not cmd: |
paul@1439 | 1592 | cmd = read_input("Period: (c)ancel, (e)dit, (u)ncancel (or return)> ") |
paul@1439 | 1593 | |
paul@1457 | 1594 | if cmd in CANCEL_PERIOD_COMMANDS: |
paul@1439 | 1595 | cl.cancel_periods([index]) |
paul@1439 | 1596 | elif cmd in EDIT_COMMANDS: |
paul@1375 | 1597 | cl.edit_period(index, args) |
paul@1439 | 1598 | elif cmd in UNCANCEL_COMMANDS: |
paul@1375 | 1599 | cl.cancel_periods([index], False) |
paul@1436 | 1600 | |
paul@1436 | 1601 | # Exit if requested or after a successful |
paul@1436 | 1602 | # operation. |
paul@1436 | 1603 | |
paul@1375 | 1604 | elif not cmd: |
paul@1375 | 1605 | pass |
paul@1375 | 1606 | else: |
paul@1375 | 1607 | continue |
paul@1375 | 1608 | break |
paul@1375 | 1609 | |
paul@1375 | 1610 | cl.show_periods() |
paul@1375 | 1611 | print |
paul@1375 | 1612 | |
paul@1375 | 1613 | # Apply suggested period (using index). |
paul@1375 | 1614 | |
paul@1439 | 1615 | elif cmd in SUGGESTED_PERIOD_COMMANDS: |
paul@1436 | 1616 | value = next_arg(args) |
paul@1436 | 1617 | index = to_int_or_none(value) |
paul@1436 | 1618 | |
paul@1436 | 1619 | if index is not None: |
paul@1375 | 1620 | cl.apply_suggested_period(index) |
paul@1436 | 1621 | |
paul@1375 | 1622 | cl.show_periods() |
paul@1375 | 1623 | print |
paul@1375 | 1624 | |
paul@1396 | 1625 | # Specify a recurrence rule. |
paul@1396 | 1626 | |
paul@1439 | 1627 | elif cmd in RULE_COMMANDS: |
paul@1436 | 1628 | value = next_arg(args) |
paul@1436 | 1629 | index = to_int_or_none(value) |
paul@1436 | 1630 | |
paul@1436 | 1631 | # Add a new rule. |
paul@1436 | 1632 | |
paul@1436 | 1633 | if index is None: |
paul@1436 | 1634 | cl.add_rule_selectors() |
paul@1436 | 1635 | else: |
paul@1436 | 1636 | cl.edit_rule_selector(index, args) |
paul@1436 | 1637 | |
paul@1436 | 1638 | cl.show_rule() |
paul@1436 | 1639 | cl.update_periods_from_rule() |
paul@1436 | 1640 | print |
paul@1396 | 1641 | |
paul@1375 | 1642 | # Set the summary. |
paul@1375 | 1643 | |
paul@1439 | 1644 | elif cmd in SUMMARY_COMMANDS: |
paul@1436 | 1645 | cl.edit_summary(get_text_arg(s)) |
paul@1375 | 1646 | cl.show_object() |
paul@1375 | 1647 | print |
paul@1375 | 1648 | |
paul@1375 | 1649 | except EOFError: |
paul@1375 | 1650 | return |
paul@1375 | 1651 | |
paul@1375 | 1652 | def main(args): |
paul@1436 | 1653 | |
paul@1436 | 1654 | """ |
paul@1436 | 1655 | The main program, employing command line 'args' to initialise the editing |
paul@1436 | 1656 | activity. |
paul@1436 | 1657 | """ |
paul@1436 | 1658 | |
paul@1375 | 1659 | global echo |
paul@1375 | 1660 | |
paul@1387 | 1661 | if "--help" in args: |
paul@1387 | 1662 | show_help(os.path.split(sys.argv[0])[-1]) |
paul@1436 | 1663 | return 0 |
paul@1387 | 1664 | |
paul@1375 | 1665 | # Parse command line arguments using the standard options plus some extra |
paul@1375 | 1666 | # options. |
paul@1375 | 1667 | |
paul@1375 | 1668 | args = parse_args(args, { |
paul@1387 | 1669 | "--calendar-data" : ("calendar_data", False), |
paul@1387 | 1670 | "--charset" : ("charset", "utf-8"), |
paul@1375 | 1671 | "--echo" : ("echo", False), |
paul@1375 | 1672 | "-f" : ("filename", None), |
paul@1387 | 1673 | "--handle-data" : ("handle_data", False), |
paul@1375 | 1674 | "--suppress-bcc" : ("suppress_bcc", False), |
paul@1375 | 1675 | "-u" : ("user", None), |
paul@1436 | 1676 | "--uid" : ("uid", None), |
paul@1436 | 1677 | "--recurrence-id" : ("recurrenceid", None), |
paul@1436 | 1678 | "--show-config" : ("show_config", False) |
paul@1375 | 1679 | }) |
paul@1375 | 1680 | |
paul@1387 | 1681 | charset = args["charset"] |
paul@1387 | 1682 | calendar_data = args["calendar_data"] |
paul@1375 | 1683 | echo = args["echo"] |
paul@1375 | 1684 | filename = args["filename"] |
paul@1387 | 1685 | handle_data = args["handle_data"] |
paul@1375 | 1686 | sender = (args["senders"] or [None])[0] |
paul@1375 | 1687 | suppress_bcc = args["suppress_bcc"] |
paul@1375 | 1688 | user = args["user"] |
paul@1436 | 1689 | uid = args["uid"] |
paul@1436 | 1690 | recurrenceid = args["recurrenceid"] |
paul@1436 | 1691 | |
paul@1436 | 1692 | # Open a store. |
paul@1436 | 1693 | |
paul@1436 | 1694 | store_type = args.get("store_type") |
paul@1436 | 1695 | store_dir = args.get("store_dir") |
paul@1436 | 1696 | preferences_dir = args.get("preferences_dir") |
paul@1436 | 1697 | |
paul@1436 | 1698 | # Show configuration and exit if requested. |
paul@1436 | 1699 | |
paul@1436 | 1700 | if args["show_config"]: |
paul@1436 | 1701 | print """\ |
paul@1436 | 1702 | Store type: %s (%s) |
paul@1436 | 1703 | Store directory: %s (%s) |
paul@1436 | 1704 | Preferences directory: %s |
paul@1436 | 1705 | """ % ( |
paul@1436 | 1706 | store_type, settings["STORE_TYPE"], |
paul@1436 | 1707 | store_dir, settings["STORE_DIR"], |
paul@1436 | 1708 | preferences_dir) |
paul@1436 | 1709 | return 0 |
paul@1375 | 1710 | |
paul@1375 | 1711 | # Determine the user and sender identities. |
paul@1375 | 1712 | |
paul@1375 | 1713 | if sender and not user: |
paul@1375 | 1714 | user = get_uri(sender) |
paul@1375 | 1715 | elif user and not sender: |
paul@1375 | 1716 | sender = get_address(user) |
paul@1375 | 1717 | elif not sender and not user: |
paul@1375 | 1718 | print >>sys.stderr, "A sender or a user must be specified." |
paul@1436 | 1719 | return 1 |
paul@1375 | 1720 | |
paul@1436 | 1721 | # Obtain a store but not a journal. |
paul@1375 | 1722 | |
paul@1375 | 1723 | store = get_store(store_type, store_dir) |
paul@1375 | 1724 | journal = None |
paul@1375 | 1725 | |
paul@1375 | 1726 | # Open a messenger for the user. |
paul@1375 | 1727 | |
paul@1375 | 1728 | messenger = Messenger(sender=sender, suppress_bcc=suppress_bcc) |
paul@1375 | 1729 | |
paul@1375 | 1730 | # Open a client for the user. |
paul@1375 | 1731 | |
paul@1375 | 1732 | cl = TextClient(user, messenger, store, journal, preferences_dir) |
paul@1375 | 1733 | |
paul@1436 | 1734 | # Read any input resource, using it to obtain identifier details. |
paul@1375 | 1735 | |
paul@1375 | 1736 | if filename: |
paul@1387 | 1737 | if calendar_data: |
paul@1387 | 1738 | all_itip = get_itip_from_data(filename, charset) |
paul@1387 | 1739 | else: |
paul@1387 | 1740 | all_itip = get_itip_from_message(filename) |
paul@1387 | 1741 | |
paul@1387 | 1742 | objects = [] |
paul@1387 | 1743 | |
paul@1387 | 1744 | # Process the objects using the person handler. |
paul@1387 | 1745 | |
paul@1387 | 1746 | if handle_data: |
paul@1387 | 1747 | for itip in all_itip: |
paul@1423 | 1748 | handled = handle_calendar_data(itip, get_handlers(cl, person.handlers, None)) |
paul@1423 | 1749 | if not is_cancel_itip(itip): |
paul@1423 | 1750 | objects += handled |
paul@1387 | 1751 | |
paul@1387 | 1752 | # Or just obtain objects from the data. |
paul@1387 | 1753 | |
paul@1387 | 1754 | else: |
paul@1387 | 1755 | for itip in all_itip: |
paul@1423 | 1756 | handled = get_objects_from_itip(itip, ["VEVENT"]) |
paul@1423 | 1757 | if not is_cancel_itip(itip): |
paul@1423 | 1758 | objects += handled |
paul@1375 | 1759 | |
paul@1375 | 1760 | # Choose an object to edit. |
paul@1375 | 1761 | |
paul@1375 | 1762 | show_objects(objects, user, store) |
paul@1375 | 1763 | obj = select_object(cl, objects) |
paul@1375 | 1764 | |
paul@1436 | 1765 | # Load any indicated object. |
paul@1375 | 1766 | |
paul@1436 | 1767 | elif uid: |
paul@1436 | 1768 | obj = cl.load_object(uid, recurrenceid) |
paul@1375 | 1769 | |
paul@1375 | 1770 | # Or create a new object. |
paul@1375 | 1771 | |
paul@1375 | 1772 | else: |
paul@1375 | 1773 | obj = cl.new_object() |
paul@1375 | 1774 | |
paul@1436 | 1775 | # Exit without any object. |
paul@1436 | 1776 | |
paul@1436 | 1777 | if not obj: |
paul@1436 | 1778 | print >>sys.stderr, "No object loaded." |
paul@1436 | 1779 | return 1 |
paul@1436 | 1780 | |
paul@1375 | 1781 | # Edit the object. |
paul@1375 | 1782 | |
paul@1387 | 1783 | edit_object(cl, obj, handle_outgoing=handle_data) |
paul@1387 | 1784 | |
paul@1387 | 1785 | def show_help(progname): |
paul@1387 | 1786 | print >>sys.stderr, help_text % progname |
paul@1387 | 1787 | |
paul@1387 | 1788 | help_text = """\ |
paul@1387 | 1789 | Usage: %s -s <sender> | -u <user> \\ |
paul@1436 | 1790 | [ -f <filename> | --uid <uid> [ --recurrence-id <recurrence-id> ] ] \\ |
paul@1387 | 1791 | [ --calendar-data --charset ] \\ |
paul@1387 | 1792 | [ --handle-data ] \\ |
paul@1387 | 1793 | [ -T <store type ] [ -S <store directory> ] \\ |
paul@1387 | 1794 | [ -p <preferences directory> ] \\ |
paul@1387 | 1795 | [ --echo ] |
paul@1387 | 1796 | |
paul@1387 | 1797 | Identity options: |
paul@1387 | 1798 | |
paul@1387 | 1799 | -s Indicate the user by specifying a sender address |
paul@1387 | 1800 | -u Indicate the user by specifying their URI |
paul@1387 | 1801 | |
paul@1387 | 1802 | Input options: |
paul@1387 | 1803 | |
paul@1436 | 1804 | -f Indicates a filename containing a MIME-encoded message or |
paul@1436 | 1805 | calendar object |
paul@1436 | 1806 | --uid Indicates the UID of a stored calendar object |
paul@1436 | 1807 | --recurrence-id Indicates a stored object with a specific RECURRENCE-ID |
paul@1387 | 1808 | |
paul@1387 | 1809 | --calendar-data Indicates that the specified file contains a calendar object |
paul@1387 | 1810 | as opposed to a mail message |
paul@1387 | 1811 | --charset Specifies the character encoding used by a calendar object |
paul@1387 | 1812 | description |
paul@1387 | 1813 | |
paul@1387 | 1814 | Processing options: |
paul@1387 | 1815 | |
paul@1387 | 1816 | --handle-data Cause the input to be handled and stored in the configured |
paul@1387 | 1817 | data store |
paul@1387 | 1818 | |
paul@1387 | 1819 | Configuration options (overriding configured defaults): |
paul@1387 | 1820 | |
paul@1387 | 1821 | -p Indicates the location of user preference directories |
paul@1387 | 1822 | -S Indicates the location of the calendar data store containing user storage |
paul@1387 | 1823 | directories |
paul@1387 | 1824 | -T Indicates the store type (the configured value if omitted) |
paul@1387 | 1825 | |
paul@1387 | 1826 | Output options: |
paul@1387 | 1827 | |
paul@1387 | 1828 | --echo Echo received input, useful if consuming input from the |
paul@1387 | 1829 | standard input stream and producing a log of the program's |
paul@1387 | 1830 | activity |
paul@1387 | 1831 | """ |
paul@1375 | 1832 | |
paul@1375 | 1833 | if __name__ == "__main__": |
paul@1436 | 1834 | sys.exit(main(sys.argv[1:])) |
paul@1375 | 1835 | |
paul@1375 | 1836 | # vim: tabstop=4 expandtab shiftwidth=4 |