icalcli 1.0.3__tar.gz → 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ # 1.0.5 released 2024-04-17
2
+
3
+ * Added `cal5w` command to provide current week, two past weeks and two future weeks in a calendar format.
4
+ * Introduced option to get more event details (end time, alarm, location, free/busy) in week and calendar views.
5
+ * Extended the file backend to allow multiple `ics` files in read only mode.
6
+
7
+ # 1.0.4 released 2022-10-11
8
+ Bug fixes
9
+
10
+ # 1.0.3 released 2022-09-19
11
+ Bug fixes
12
+
13
+ # 1.0.2 released 2022-08-17
14
+ Bug fixes
15
+
16
+ # 1.0.1 released 2022-08-16
17
+ Bug fixes
18
+
19
+ # 1.0.0 released 2022-08-16
20
+
21
+ ## Added
22
+
23
+ * Added support for recurring events using [python-recurring-ical-events ](https://github.com/niccokunzmann/python-recurring-ical-events).
24
+ * It is also possible to create and edit recurring events The recurrence rule has to be given as a string using the `--rrule` option to the `add` and `edit` commands (for example `--rrule FREQ=DAILY;COUNT=5`). Editing/deleting modifies/deletes all occurrences of the event. To delete a single occurrence, use `--exdate`.
25
+ * Added support for creating/editing events using raw ICS text. This is mainly for properties (like `attendee` and `comment`) that are not available as command line options.
26
+ * If duplicate UIDs are found, they are de-duplicated and the calendar is opened in read only mode. Editing a file with multiple UIDs is dangerous.
27
+
28
+ ## Changed
29
+
30
+ * Searching for events without any start or end parameters now returns event occurrences only within the last 5 years and next 5 years (this number can be changed with the option `--default_past_years` and `--default_future_years`). More events can be obtained using explicit start and end parameters. For example, `s home '-10 y' 15y` will go back 10 years and forward 15 years. This change was necessitated because a single recurring event (without any `COUNT` or `UNTIL` restrictions) can generate an infinite number of occurrences.
31
+ * The `-u` or `--uid` option has been removed and replaced by a more general `-p` or `--property` option that allows any property of the event to be searched. For example, `s -p rrule DAILY` will display all events with a recurrence rule that has a daily frequency.
32
+ * Many Bug fixes.
33
+ * Several method names were changed (the leading underscore was dropped from all method names), but these were intended to be used only internally.
34
+
35
+ # 0.9.9 released 2022-07-12
36
+
37
+ ## Added
38
+
39
+ * Check for errors while parsing `iCalendar` events
40
+ * Added option to provide stack trace for errors. By default, most errors are caught using try blocks and only a cryptic summary is provided. With this option, the stack trace is printed, but the program continues to run.
41
+
42
+ ## Changed
43
+
44
+ * A few bug fixes
45
+
@@ -1,12 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: icalcli
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Icalendar Calendar Command Line Interface
5
5
  Home-page: https://github.com/jrvarma/icalcli
6
6
  Maintainer: Jayanth R. Varma
7
7
  Maintainer-email: jrvarma@gmail.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Classifier: Development Status :: 3 - Alpha
11
9
  Classifier: Environment :: Console
12
10
  Classifier: Intended Audience :: End Users/Desktop
@@ -68,6 +66,7 @@ python setup.py install
68
66
  edit (e) edit calendar events
69
67
  agenda (g) get an agenda for a time period
70
68
  calw (w) get a week-based agenda in calendar format
69
+ cal5w (5w) get this week / 2 past / 2 future weeks agenda in calendar format
71
70
  calm (m) get a month agenda in calendar format
72
71
  add (a) add a detailed event to the calendar
73
72
  search (s) (regex) search for events
@@ -89,6 +88,15 @@ backend_interface = ICSInterface("/path/to/ics-file")
89
88
 
90
89
  ```
91
90
 
91
+ #### Example configuration for multiple readonly file_backend
92
+
93
+ ```
94
+ from icalcli import ICSInterface
95
+
96
+ backend_interface = ICSInterface(["/path/to/ics-file-1", "/path/to/ics-file-2"])
97
+
98
+ ```
99
+
92
100
  #### Example configuration for etesync_backend
93
101
 
94
102
  This configuration assumes that all the credentials are stored in a plain text (`json`) file. In practice, you would use a more secure storage (perhaps, the `Gnome keyring`) or just read it from the terminal.
@@ -108,6 +116,16 @@ backend_interface = EtesyncInterface(
108
116
  ```
109
117
  See the [Example code](https://github.com/jrvarma/icalcli/issues/1#issuecomment-979851222) for getting the `uid` and `authToken` for the `etesync` calendar.
110
118
 
119
+ ## Recurring events and default search period
120
+
121
+ `icalcli` understands the `RRULE`, `RDATE`, `EXRULE`, `EXDATE` elements of the `icalendar` specification. These elements can be added while creating or editing events using `--rrule`, `--rdate`, `--exrule` and `--exdate` options.
122
+
123
+ In most views, the instances of the recurring event are displayed. Since a recurring event can have an unlimited number of instances, searches with no start or end date can produce an unending series of events. By default therefore searches with no start or end date are limited to the previous five years and following five years. These defaults can be changed using the options ` --default_past_years` and `--default_future_years`
124
+
125
+ ## Raw ICS
126
+
127
+ The `icalendar` specification is quite large and complex, and `icalcli` implements only the most common parts of this specification. It is possible to use the `--raw_ics` option to create/edit event using raw ICS text.
128
+
111
129
  ## Screenshots
112
130
 
113
131
  Some screenshots are available at Github:
@@ -131,5 +149,3 @@ Some screenshots are available at Github:
131
149
  #### Month View
132
150
 
133
151
  ![Month View](https://github.com/jrvarma/icalcli/raw/master/screenshots/icalci-4-month-view.png)
134
-
135
-
@@ -49,6 +49,7 @@ python setup.py install
49
49
  edit (e) edit calendar events
50
50
  agenda (g) get an agenda for a time period
51
51
  calw (w) get a week-based agenda in calendar format
52
+ cal5w (5w) get this week / 2 past / 2 future weeks agenda in calendar format
52
53
  calm (m) get a month agenda in calendar format
53
54
  add (a) add a detailed event to the calendar
54
55
  search (s) (regex) search for events
@@ -70,6 +71,15 @@ backend_interface = ICSInterface("/path/to/ics-file")
70
71
 
71
72
  ```
72
73
 
74
+ #### Example configuration for multiple readonly file_backend
75
+
76
+ ```
77
+ from icalcli import ICSInterface
78
+
79
+ backend_interface = ICSInterface(["/path/to/ics-file-1", "/path/to/ics-file-2"])
80
+
81
+ ```
82
+
73
83
  #### Example configuration for etesync_backend
74
84
 
75
85
  This configuration assumes that all the credentials are stored in a plain text (`json`) file. In practice, you would use a more secure storage (perhaps, the `Gnome keyring`) or just read it from the terminal.
@@ -89,6 +99,16 @@ backend_interface = EtesyncInterface(
89
99
  ```
90
100
  See the [Example code](https://github.com/jrvarma/icalcli/issues/1#issuecomment-979851222) for getting the `uid` and `authToken` for the `etesync` calendar.
91
101
 
102
+ ## Recurring events and default search period
103
+
104
+ `icalcli` understands the `RRULE`, `RDATE`, `EXRULE`, `EXDATE` elements of the `icalendar` specification. These elements can be added while creating or editing events using `--rrule`, `--rdate`, `--exrule` and `--exdate` options.
105
+
106
+ In most views, the instances of the recurring event are displayed. Since a recurring event can have an unlimited number of instances, searches with no start or end date can produce an unending series of events. By default therefore searches with no start or end date are limited to the previous five years and following five years. These defaults can be changed using the options ` --default_past_years` and `--default_future_years`
107
+
108
+ ## Raw ICS
109
+
110
+ The `icalendar` specification is quite large and complex, and `icalcli` implements only the most common parts of this specification. It is possible to use the `--raw_ics` option to create/edit event using raw ICS text.
111
+
92
112
  ## Screenshots
93
113
 
94
114
  Some screenshots are available at Github:
icalcli-1.0.5/exclude ADDED
@@ -0,0 +1,7 @@
1
+ **build**
2
+ **dist**
3
+ **file_backend_n_screenshots**
4
+ **egg-info**
5
+ **__pycache__**
6
+ exclude
7
+ update-github.md
@@ -1,5 +1,5 @@
1
1
  __program__ = 'icalcli'
2
- __version__ = 'v1.0.3'
2
+ __version__ = 'v1.0.4'
3
3
  __author__ = 'Jayanth Varma (modified gcalcli by Eric Davis et al.)'
4
4
  from icalcli.icalcli import IcalendarInterface # noqa F401
5
5
  try:
@@ -263,6 +263,11 @@ def get_argument_parser(initial=True):
263
263
  parents=[outputs_parser, output_parser, cal_query_parser])
264
264
  calw.add_argument('-n', "--weeks", type=int, default=2, nargs="?")
265
265
 
266
+ cal5w = sub.add_parser(
267
+ "cal5w", aliases=['5w'],
268
+ parents=[outputs_parser, output_parser, cal_query_parser])
269
+ cal5w.add_argument('-n', "--weeks", type=int, default=2, nargs="?")
270
+
266
271
  sub.add_parser(
267
272
  "calm", aliases=['m'],
268
273
  parents=[outputs_parser, output_parser, cal_query_parser])
@@ -12,10 +12,14 @@ class ICSInterface:
12
12
  filename : path to ics file
13
13
  backup: boolean whether to back up old file before overwriting
14
14
  """
15
- self.filepath = Path(filename).resolve()
15
+ if isinstance(filename, list):
16
+ self.filepaths = [Path(f).resolve() for f in filename]
17
+ self.readonly = True
18
+ else:
19
+ self.filepaths = [Path(filename).resolve()]
20
+ self.readonly = False
16
21
  self.backup = backup
17
- with open(self.filepath, 'r') as fp:
18
- self.ics = fp.read()
22
+ self.ics = []
19
23
  self.all_events()
20
24
 
21
25
  def all_events(self):
@@ -26,13 +30,20 @@ class ICSInterface:
26
30
  return False
27
31
  else:
28
32
  return True
29
- self.calendar = Calendar.from_ical(self.ics)
30
- self.events = [ev for ev in self.calendar.walk('VEVENT')
31
- if check_event(ev)]
33
+ self.events = []
32
34
  self.cache_events = {}
33
- for ev in self.events:
34
- uid = ev.decoded('uid').decode()
35
- self.cache_events[uid] = ev
35
+ filecount = 0
36
+ for path in self.filepaths:
37
+ with open(path, 'r') as fp:
38
+ cal = Calendar.from_ical(fp.read())
39
+ events = [ev for ev in cal.walk('VEVENT') if check_event(ev)]
40
+ self.events += events
41
+ for ev in events:
42
+ uid = ev.decoded('uid').decode()
43
+ if len(self.filepaths) > 1:
44
+ uid = f"File{filecount}:{uid}"
45
+ self.cache_events[uid] = ev
46
+ filecount += 1
36
47
 
37
48
  def create_event(self, event, vtimezone=None):
38
49
  uid = event.decoded('uid').decode()
@@ -46,6 +57,8 @@ class ICSInterface:
46
57
  del self.cache_events[uid]
47
58
 
48
59
  def sync(self, vtimezone=None):
60
+ if self.readonly:
61
+ return
49
62
  if self.backup:
50
63
  with NamedTemporaryFile(mode='w',
51
64
  suffix=self.filepath.suffix,
@@ -95,6 +95,7 @@ class IcalendarInterface:
95
95
  UNIWIDTH = {'W': 2, 'F': 2, 'N': 1, 'Na': 1, 'H': 1, 'A': 1}
96
96
  backend_cache_dirty = False
97
97
  default_outputs = ['end', 'alarms', 'freebusy', 'location']
98
+ default_graphical_outputs = ['end', 'alarms']
98
99
  no_past_events = 5
99
100
  no_future_events = 10
100
101
 
@@ -104,6 +105,7 @@ class IcalendarInterface:
104
105
  self.cals = []
105
106
  self.printer = printer
106
107
  self.initial_options = options
108
+ self.initial_options['stack_trace'] = True
107
109
  self.set_options(options)
108
110
  self.add_parser = add_parser
109
111
  self.backend_interface = backend_interface
@@ -128,6 +130,10 @@ class IcalendarInterface:
128
130
  for key in self.default_outputs:
129
131
  if key not in self.outputs:
130
132
  self.outputs[key] = True
133
+ self.graphical_outputs = options.get('outputs', {})
134
+ for key in self.default_graphical_outputs:
135
+ if key not in self.outputs:
136
+ self.graphical_outputs[key] = True
131
137
 
132
138
  def set_now(self):
133
139
  # This command is run at initiaization, but it should also be
@@ -157,6 +163,12 @@ class IcalendarInterface:
157
163
  self.readonly = True
158
164
  self.printer.err_msg('Duplicate UIDs found. '
159
165
  'Calendar deduplicated and set to readonly\n')
166
+ elif (
167
+ hasattr(self.backend_interface, 'readonly')
168
+ and self.backend_interface.readonly
169
+ ):
170
+ self.readonly = True
171
+ self.printer.err_msg('Calendar backend interface is readonly\n')
160
172
  else:
161
173
  self.readonly = False
162
174
 
@@ -323,7 +335,7 @@ class IcalendarInterface:
323
335
  """
324
336
  return e_start < time_point and e_end >= time_point
325
337
 
326
- def format_title(self, event, allday=False):
338
+ def event_summary(self, event, allday=False):
327
339
  r"""Return event title (summary and start time)
328
340
 
329
341
  Parameters
@@ -335,19 +347,52 @@ class IcalendarInterface:
335
347
  -------
336
348
  string
337
349
  """
350
+ def fmt_time(field):
351
+ if self.options['military']:
352
+ return self.decode_dtm(event, field).strftime("%H:%M")
353
+ else:
354
+ return (
355
+ self.decode_dtm(event, field).strftime("%I:%M")
356
+ .lstrip('0') +
357
+ self.decode_dtm(event, field).strftime('%p') .lower())
338
358
  titlestr = self.valid_title(event)
339
359
  if allday:
340
- return titlestr
341
- elif self.options['military']:
342
- return ' '.join(
343
- [self.decode_dtm(event, 'dtstart').strftime("%H:%M"),
344
- titlestr])
360
+ summary = titlestr
345
361
  else:
346
- return ' '.join([
347
- self.decode_dtm(event, 'dtstart').strftime("%I:%M")
348
- .lstrip('0') +
349
- self.decode_dtm(event, 'dtstart').strftime('%p')
350
- .lower(), titlestr])
362
+ tm = fmt_time('dtstart')
363
+ if self.graphical_outputs.get('end'):
364
+ tm += " - " + fmt_time('dtend')
365
+ summary = tm + " " + titlestr
366
+ if self.graphical_outputs.get('alarms'):
367
+ alarms = event.walk('valarm')
368
+ if alarms:
369
+ if isinstance(alarms[0].Decoded('trigger'), timedelta):
370
+ minutes = -(alarms[0].Decoded('trigger')
371
+ .total_seconds()/60)
372
+ summary += ' AL:%.0fm' % minutes
373
+ else:
374
+ summary += ' AL: ??'
375
+ if self.graphical_outputs.get('freebusy'):
376
+ free = ('transp' in event and
377
+ event.Decoded('transp').decode() == 'TRANSPARENT')
378
+ summary += (' free ' if free else ' busy ')
379
+ if (
380
+ self.graphical_outputs.get('location')
381
+ and 'location' in event
382
+ and event.Decoded('location').decode().strip()
383
+ ):
384
+ summary += " [%s]" % (event.Decoded('location').
385
+ decode().strip())
386
+ if self.graphical_outputs.get('uid'):
387
+ summary += " <%s>" % (event.Decoded('uid').decode().strip())
388
+ # if (
389
+ # self.graphical_outputs.get('description')
390
+ # and 'description' in event
391
+ # and event.Decoded('description').decode().strip()
392
+ # and self.outputs.get('description')
393
+ # ):
394
+ # summary += event.Decoded('description'). decode().strip()
395
+ return summary
351
396
 
352
397
  def get_week_events(self, start_dt, end_dt, event_list):
353
398
  r"""Returns all events during a week (start_dt to end_dt)
@@ -401,9 +446,11 @@ class IcalendarInterface:
401
446
  force_now_marker = False
402
447
 
403
448
  if to_show_now:
404
- if(self.now >= event_start_date
405
- and self.now <= event_end_date
406
- and not event_allday):
449
+ if (
450
+ self.now >= event_start_date
451
+ and self.now <= event_end_date
452
+ and not event_allday
453
+ ):
407
454
  # line marker is during event (recolor event)
408
455
  force_now_marker = True
409
456
  to_show_now = False
@@ -429,7 +476,7 @@ class IcalendarInterface:
429
476
  event_color = 'default'
430
477
  # NOTE(slawqo): for all day events it's necessary to
431
478
  # add event to more than one day in week_events
432
- titlestr = self.format_title(event, allday=event_allday)
479
+ summary = self.event_summary(event, allday=event_allday)
433
480
  if event_allday and event_start_date < event_end_date:
434
481
  if event_end_date >= end_dt:
435
482
  end_daynum = 6
@@ -444,12 +491,12 @@ class IcalendarInterface:
444
491
  # event_daynum = 0
445
492
  for day in range(start_daynum, end_daynum + 1):
446
493
  week_events[day].append(
447
- EventTitle('\n' + titlestr, event_color))
494
+ EventTitle('\n' + summary, event_color))
448
495
  else:
449
496
  # newline and empty string are the keys to turn off
450
497
  # coloring
451
498
  week_events[event_daynum].append(
452
- EventTitle('\n' + titlestr, event_color))
499
+ EventTitle('\n' + summary, event_color))
453
500
  return week_events
454
501
 
455
502
  def printed_len(self, string):
@@ -509,8 +556,10 @@ class IcalendarInterface:
509
556
  words = _u(string).split()
510
557
  for i, word in enumerate(words):
511
558
  word_len = self.printed_len(word)
512
- if ((cur_print_len + word_len + print_len
513
- ) >= self.options['cal_width']):
559
+ if (
560
+ (cur_print_len + word_len + print_len
561
+ ) >= self.options['cal_width']
562
+ ):
514
563
  cut_idx = len(' '.join(words[:i]))
515
564
  # if the word is too long,
516
565
  # we cannot cut between words
@@ -553,7 +602,7 @@ class IcalendarInterface:
553
602
 
554
603
  Parameters
555
604
  ----------
556
- cmd : Command ('calw' or 'calm' for week and month)
605
+ cmd : Command ('calw', 'cal5w' or 'calm' for week, 5weeks and month)
557
606
  startDateTime : datetime (start date)
558
607
  count : int (number of weeks or months)
559
608
  eventList : list of icalendar events
@@ -637,7 +686,7 @@ class IcalendarInterface:
637
686
  for i in range(count):
638
687
  # create and print the date line for a week
639
688
  for j in range(days):
640
- if cmd == 'calw':
689
+ if cmd in ('calw', 'cal5w'):
641
690
  d = (startWeekDateTime +
642
691
  timedelta(days=j)).strftime("%d %b")
643
692
  else: # (cmd == 'calm'):
@@ -648,9 +697,11 @@ class IcalendarInterface:
648
697
  d = ''
649
698
  tmpDateColor = self.options['color_date']
650
699
 
651
- if (self.now.strftime("%d%b%Y") == (
652
- (startWeekDateTime + timedelta(days=j))
653
- .strftime("%d%b%Y"))):
700
+ if (
701
+ self.now.strftime("%d%b%Y") == (
702
+ (startWeekDateTime + timedelta(days=j))
703
+ .strftime("%d%b%Y"))
704
+ ):
654
705
  tmpDateColor = self.options['color_now_marker']
655
706
  d += " **"
656
707
 
@@ -834,9 +885,11 @@ class IcalendarInterface:
834
885
  self.printer.msg(' %s' % self.valid_title(event).strip(),
835
886
  eventColor)
836
887
 
837
- if(self.outputs.get('location')
838
- and 'location' in event
839
- and event.Decoded('location').decode().strip()):
888
+ if (
889
+ self.outputs.get('location')
890
+ and 'location' in event
891
+ and event.Decoded('location').decode().strip()
892
+ ):
840
893
  xstr = " [%s]" % (event.Decoded('location').
841
894
  decode().strip())
842
895
  self.printer.msg(xstr, 'default')
@@ -847,8 +900,10 @@ class IcalendarInterface:
847
900
 
848
901
  self.printer.msg('\n')
849
902
 
850
- if(self.outputs.get('description') and 'description' in event
851
- and event.Decoded('description').decode().strip()):
903
+ if (
904
+ self.outputs.get('description') and 'description' in event
905
+ and event.Decoded('description').decode().strip()
906
+ ):
852
907
  descrIndent = outputsIndent + ' '
853
908
  box = True # leave old non-box code for option later
854
909
  if box:
@@ -915,8 +970,10 @@ class IcalendarInterface:
915
970
  day = ''
916
971
 
917
972
  for event in eventList:
918
- if(self.options['ignore_started'] and
919
- (self.decode_dtm(event, 'dtstart') < self.now)):
973
+ if (
974
+ self.options['ignore_started'] and
975
+ (self.decode_dtm(event, 'dtstart') < self.now)
976
+ ):
920
977
  continue
921
978
  if self.options['ignore_declined'] and self._DeclinedEvent(
922
979
  event):
@@ -1158,8 +1215,14 @@ class IcalendarInterface:
1158
1215
  # convert start date to the beginning of the week or month
1159
1216
  if cmd == 'calw':
1160
1217
  dayNum = self.cal_monday(int(start.strftime("%w")))
1161
- start = (start - timedelta(days=dayNum))
1162
- end = (start + timedelta(days=(count * 7)))
1218
+ start -= timedelta(days=dayNum)
1219
+ weeks = count
1220
+ end = (start + timedelta(days=(weeks * 7)))
1221
+ elif cmd == 'cal5w':
1222
+ dayNum = self.cal_monday(int(start.strftime("%w")))
1223
+ start -= timedelta(days=(dayNum + count * 7))
1224
+ weeks = 2 * count + 1
1225
+ end = (start + timedelta(days=(weeks * 7)))
1163
1226
  else: # cmd == 'calm':
1164
1227
  start = (start - timedelta(days=(start.day - 1)))
1165
1228
  endMonth = (start.month + 1)
@@ -1175,13 +1238,13 @@ class IcalendarInterface:
1175
1238
  if offsetDays < 0:
1176
1239
  offsetDays = 6
1177
1240
  totalDays = (daysInMonth + offsetDays)
1178
- count = int(totalDays / 7)
1241
+ weeks = int(totalDays / 7)
1179
1242
  if totalDays % 7:
1180
- count += 1
1243
+ weeks += 1
1181
1244
 
1182
1245
  eventList = self.search_for_events(start, end, None)
1183
1246
 
1184
- self.GraphEvents(cmd, start, count, eventList)
1247
+ self.GraphEvents(cmd, start, weeks, eventList)
1185
1248
 
1186
1249
  def sync(self):
1187
1250
  r"""Sync calendar
@@ -1392,7 +1455,8 @@ class IcalendarInterface:
1392
1455
  default_event_duration = timedelta(minutes=30)
1393
1456
  old = None
1394
1457
  if original:
1395
- old = original.copy()
1458
+ # simulate old = original.deepcopy()
1459
+ old = Calendar.from_ical(original.to_ical().decode())
1396
1460
  uid = old.Decoded('uid').decode()
1397
1461
  old_start = self.display_timezone(old.Decoded('dtstart'))
1398
1462
  old_duration = (
@@ -1400,8 +1464,10 @@ class IcalendarInterface:
1400
1464
  (old.Decoded('dtend') - old.Decoded('dtstart')))
1401
1465
  if not args.summary:
1402
1466
  args.summary = old.Decoded('summary').decode()
1403
- if(not args.time and not (args.start and 'T' in args.start)
1404
- and self.isallday(old)):
1467
+ if (
1468
+ not args.time and not (args.start and 'T' in args.start)
1469
+ and self.isallday(old)
1470
+ ):
1405
1471
  args.allday = True
1406
1472
  day = args.day or old_start.day
1407
1473
  month = args.month or old_start.month
@@ -1689,6 +1755,10 @@ def repl(ecal=None):
1689
1755
  ecal.CalQuery(
1690
1756
  'calw', count=FLAGS.weeks, startText=FLAGS.start)
1691
1757
 
1758
+ elif FLAGS.command in ['5w', 'cal5w']:
1759
+ ecal.CalQuery(
1760
+ 'cal5w', count=FLAGS.weeks, startText=FLAGS.start)
1761
+
1692
1762
  elif FLAGS.command in ['m', 'calm']:
1693
1763
  ecal.CalQuery('calm', startText=FLAGS.start)
1694
1764
 
@@ -1715,9 +1785,11 @@ def repl(ecal=None):
1715
1785
  field=FLAGS.property)
1716
1786
 
1717
1787
  elif FLAGS.command in ['q', 'quit']:
1718
- if(ecal.backend_cache_dirty and ecal.no_auto_sync
1719
- and not IcalendarInterface.confirm(
1720
- "Changes made in calendar not yet synced. Quit y/n? ")):
1788
+ if (
1789
+ ecal.backend_cache_dirty and ecal.no_auto_sync
1790
+ and not IcalendarInterface.confirm(
1791
+ "Changes made in calendar not yet synced. Quit y/n? ")
1792
+ ):
1721
1793
  pass
1722
1794
  else:
1723
1795
  ecal.interactive = False
@@ -1,12 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: icalcli
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Icalendar Calendar Command Line Interface
5
5
  Home-page: https://github.com/jrvarma/icalcli
6
6
  Maintainer: Jayanth R. Varma
7
7
  Maintainer-email: jrvarma@gmail.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Classifier: Development Status :: 3 - Alpha
11
9
  Classifier: Environment :: Console
12
10
  Classifier: Intended Audience :: End Users/Desktop
@@ -68,6 +66,7 @@ python setup.py install
68
66
  edit (e) edit calendar events
69
67
  agenda (g) get an agenda for a time period
70
68
  calw (w) get a week-based agenda in calendar format
69
+ cal5w (5w) get this week / 2 past / 2 future weeks agenda in calendar format
71
70
  calm (m) get a month agenda in calendar format
72
71
  add (a) add a detailed event to the calendar
73
72
  search (s) (regex) search for events
@@ -89,6 +88,15 @@ backend_interface = ICSInterface("/path/to/ics-file")
89
88
 
90
89
  ```
91
90
 
91
+ #### Example configuration for multiple readonly file_backend
92
+
93
+ ```
94
+ from icalcli import ICSInterface
95
+
96
+ backend_interface = ICSInterface(["/path/to/ics-file-1", "/path/to/ics-file-2"])
97
+
98
+ ```
99
+
92
100
  #### Example configuration for etesync_backend
93
101
 
94
102
  This configuration assumes that all the credentials are stored in a plain text (`json`) file. In practice, you would use a more secure storage (perhaps, the `Gnome keyring`) or just read it from the terminal.
@@ -108,6 +116,16 @@ backend_interface = EtesyncInterface(
108
116
  ```
109
117
  See the [Example code](https://github.com/jrvarma/icalcli/issues/1#issuecomment-979851222) for getting the `uid` and `authToken` for the `etesync` calendar.
110
118
 
119
+ ## Recurring events and default search period
120
+
121
+ `icalcli` understands the `RRULE`, `RDATE`, `EXRULE`, `EXDATE` elements of the `icalendar` specification. These elements can be added while creating or editing events using `--rrule`, `--rdate`, `--exrule` and `--exdate` options.
122
+
123
+ In most views, the instances of the recurring event are displayed. Since a recurring event can have an unlimited number of instances, searches with no start or end date can produce an unending series of events. By default therefore searches with no start or end date are limited to the previous five years and following five years. These defaults can be changed using the options ` --default_past_years` and `--default_future_years`
124
+
125
+ ## Raw ICS
126
+
127
+ The `icalendar` specification is quite large and complex, and `icalcli` implements only the most common parts of this specification. It is possible to use the `--raw_ics` option to create/edit event using raw ICS text.
128
+
111
129
  ## Screenshots
112
130
 
113
131
  Some screenshots are available at Github:
@@ -131,5 +149,3 @@ Some screenshots are available at Github:
131
149
  #### Month View
132
150
 
133
151
  ![Month View](https://github.com/jrvarma/icalcli/raw/master/screenshots/icalci-4-month-view.png)
134
-
135
-
@@ -1,6 +1,9 @@
1
+ CHANGELOG.md
1
2
  LICENSE
2
3
  README.md
4
+ exclude
3
5
  setup.py
6
+ update-github.md
4
7
  icalcli/__init__.py
5
8
  icalcli/argparsers.py
6
9
  icalcli/icalcli.py
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
  long_description = fh.read()
6
6
 
7
7
  setup(name='icalcli',
8
- version='1.0.3',
8
+ version='1.0.5',
9
9
  maintainer='Jayanth R. Varma',
10
10
  maintainer_email='jrvarma@gmail.com',
11
11
  description='Icalendar Calendar Command Line Interface',
@@ -0,0 +1,33 @@
1
+ # After updating local file in /0/bp/
2
+
3
+ * Update local github folder with remote by Pull from origin/master
4
+ * `rsync -avc --dry-run --exclude-from ~/0/bp/icalcli/exclude \
5
+ /home/jrvarma/0/bp/icalcli/ /home/jrvarma/0/src/github/icalcli/`
6
+ * `rsync -avc --exclude-from ~/0/bp/icalcli/exclude \
7
+ /home/jrvarma/0/bp/icalcli/ /home/jrvarma/0/src/github/icalcli/`
8
+ * Commit changes in local github folder
9
+ * Push to remote origin/master
10
+
11
+ # After updating repo
12
+
13
+ * `rsync -avc --dry-run --exclude '.git/' --exclude screenshots\
14
+ ~/0/src/github/icalcli/ ~/0/bp/icalcli/ `
15
+ * `rsync -avc --exclude '.git/' \
16
+ ~/0/src/github/icalcli/ ~/0/bp/icalcli/ `
17
+
18
+ ## Update PyPi
19
+
20
+ * Bump version number (Edit `setup.py`)
21
+
22
+ ```
23
+ cd ~/0/bp/icalcli
24
+ rm /0/bp/icalcli/dist/*
25
+ python3 setup.py sdist bdist_wheel
26
+ python3 -m twine upload dist/*
27
+ ```
28
+
29
+ ## Install new version from PyPi
30
+
31
+ ```
32
+ pip install --upgrade icalcli
33
+ ```
File without changes
File without changes
File without changes
File without changes