icalcli 1.0.4__tar.gz → 1.0.6__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.
- {icalcli-1.0.4/icalcli.egg-info → icalcli-1.0.6}/PKG-INFO +27 -2
- icalcli-1.0.4/PKG-INFO → icalcli-1.0.6/README.md +20 -17
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/argparsers.py +5 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/file_backend/ics_interface.py +22 -9
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/icalcli.py +115 -44
- icalcli-1.0.4/README.md → icalcli-1.0.6/icalcli.egg-info/PKG-INFO +42 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/setup.py +1 -1
- {icalcli-1.0.4 → icalcli-1.0.6}/LICENSE +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/__init__.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/etesync_backend/__init__.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/etesync_backend/etesync_crud.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/etesync_backend/etesync_interface.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/file_backend/__init__.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/printer.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli/utils.py +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli.egg-info/SOURCES.txt +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli.egg-info/dependency_links.txt +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli.egg-info/entry_points.txt +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli.egg-info/requires.txt +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/icalcli.egg-info/top_level.txt +0 -0
- {icalcli-1.0.4 → icalcli-1.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: icalcli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: Icalendar Calendar Command Line Interface
|
|
5
5
|
Home-page: https://github.com/jrvarma/icalcli
|
|
6
6
|
Maintainer: Jayanth R. Varma
|
|
@@ -12,8 +12,13 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Requires-Python: >=3.6
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Provides-Extra: parsedatetime
|
|
16
15
|
License-File: LICENSE
|
|
16
|
+
Requires-Dist: python-dateutil
|
|
17
|
+
Requires-Dist: parsedatetime
|
|
18
|
+
Requires-Dist: icalendar
|
|
19
|
+
Requires-Dist: recurring_ical_events
|
|
20
|
+
Provides-Extra: parsedatetime
|
|
21
|
+
Requires-Dist: parsedatetime; extra == "parsedatetime"
|
|
17
22
|
|
|
18
23
|
# icalcli
|
|
19
24
|
|
|
@@ -66,6 +71,7 @@ python setup.py install
|
|
|
66
71
|
edit (e) edit calendar events
|
|
67
72
|
agenda (g) get an agenda for a time period
|
|
68
73
|
calw (w) get a week-based agenda in calendar format
|
|
74
|
+
cal5w (5w) get this week / 2 past / 2 future weeks agenda in calendar format
|
|
69
75
|
calm (m) get a month agenda in calendar format
|
|
70
76
|
add (a) add a detailed event to the calendar
|
|
71
77
|
search (s) (regex) search for events
|
|
@@ -87,6 +93,15 @@ backend_interface = ICSInterface("/path/to/ics-file")
|
|
|
87
93
|
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
#### Example configuration for multiple readonly file_backend
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
from icalcli import ICSInterface
|
|
100
|
+
|
|
101
|
+
backend_interface = ICSInterface(["/path/to/ics-file-1", "/path/to/ics-file-2"])
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
90
105
|
#### Example configuration for etesync_backend
|
|
91
106
|
|
|
92
107
|
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.
|
|
@@ -106,6 +121,16 @@ backend_interface = EtesyncInterface(
|
|
|
106
121
|
```
|
|
107
122
|
See the [Example code](https://github.com/jrvarma/icalcli/issues/1#issuecomment-979851222) for getting the `uid` and `authToken` for the `etesync` calendar.
|
|
108
123
|
|
|
124
|
+
## Recurring events and default search period
|
|
125
|
+
|
|
126
|
+
`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.
|
|
127
|
+
|
|
128
|
+
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`
|
|
129
|
+
|
|
130
|
+
## Raw ICS
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
109
134
|
## Screenshots
|
|
110
135
|
|
|
111
136
|
Some screenshots are available at Github:
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: icalcli
|
|
3
|
-
Version: 1.0.4
|
|
4
|
-
Summary: Icalendar Calendar Command Line Interface
|
|
5
|
-
Home-page: https://github.com/jrvarma/icalcli
|
|
6
|
-
Maintainer: Jayanth R. Varma
|
|
7
|
-
Maintainer-email: jrvarma@gmail.com
|
|
8
|
-
Classifier: Development Status :: 3 - Alpha
|
|
9
|
-
Classifier: Environment :: Console
|
|
10
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Requires-Python: >=3.6
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
Provides-Extra: parsedatetime
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
|
|
18
1
|
# icalcli
|
|
19
2
|
|
|
20
3
|
### An iCalendar Command Line Interface
|
|
@@ -66,6 +49,7 @@ python setup.py install
|
|
|
66
49
|
edit (e) edit calendar events
|
|
67
50
|
agenda (g) get an agenda for a time period
|
|
68
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
|
|
69
53
|
calm (m) get a month agenda in calendar format
|
|
70
54
|
add (a) add a detailed event to the calendar
|
|
71
55
|
search (s) (regex) search for events
|
|
@@ -87,6 +71,15 @@ backend_interface = ICSInterface("/path/to/ics-file")
|
|
|
87
71
|
|
|
88
72
|
```
|
|
89
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
|
+
|
|
90
83
|
#### Example configuration for etesync_backend
|
|
91
84
|
|
|
92
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.
|
|
@@ -106,6 +99,16 @@ backend_interface = EtesyncInterface(
|
|
|
106
99
|
```
|
|
107
100
|
See the [Example code](https://github.com/jrvarma/icalcli/issues/1#issuecomment-979851222) for getting the `uid` and `authToken` for the `etesync` calendar.
|
|
108
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
|
+
|
|
109
112
|
## Screenshots
|
|
110
113
|
|
|
111
114
|
Some screenshots are available at Github:
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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(
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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' +
|
|
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' +
|
|
499
|
+
EventTitle('\n' + summary, event_color))
|
|
453
500
|
return week_events
|
|
454
501
|
|
|
455
502
|
def printed_len(self, string):
|
|
@@ -504,20 +551,23 @@ class IcalendarInterface:
|
|
|
504
551
|
-------
|
|
505
552
|
int: where to cut the word
|
|
506
553
|
"""
|
|
507
|
-
print_len = 0
|
|
554
|
+
# print_len = 0
|
|
508
555
|
|
|
509
556
|
words = _u(string).split()
|
|
510
557
|
for i, word in enumerate(words):
|
|
558
|
+
print_len = self.printed_len(' '.join(words[:i]))
|
|
511
559
|
word_len = self.printed_len(word)
|
|
512
|
-
if (
|
|
513
|
-
|
|
560
|
+
if (
|
|
561
|
+
(cur_print_len + word_len + print_len
|
|
562
|
+
) >= self.options['cal_width']
|
|
563
|
+
):
|
|
514
564
|
cut_idx = len(' '.join(words[:i]))
|
|
515
565
|
# if the word is too long,
|
|
516
566
|
# we cannot cut between words
|
|
517
567
|
if cut_idx == 0:
|
|
518
568
|
return self.word_cut(word)
|
|
519
569
|
return (print_len, cut_idx)
|
|
520
|
-
print_len += word_len + i # +i for the space between words
|
|
570
|
+
# print_len += word_len + i # +i for the space between words
|
|
521
571
|
return (print_len, len(' '.join(words[:i])))
|
|
522
572
|
|
|
523
573
|
def get_cut_index(self, event_string):
|
|
@@ -553,7 +603,7 @@ class IcalendarInterface:
|
|
|
553
603
|
|
|
554
604
|
Parameters
|
|
555
605
|
----------
|
|
556
|
-
cmd : Command ('calw' or 'calm' for week and month)
|
|
606
|
+
cmd : Command ('calw', 'cal5w' or 'calm' for week, 5weeks and month)
|
|
557
607
|
startDateTime : datetime (start date)
|
|
558
608
|
count : int (number of weeks or months)
|
|
559
609
|
eventList : list of icalendar events
|
|
@@ -637,7 +687,7 @@ class IcalendarInterface:
|
|
|
637
687
|
for i in range(count):
|
|
638
688
|
# create and print the date line for a week
|
|
639
689
|
for j in range(days):
|
|
640
|
-
if cmd
|
|
690
|
+
if cmd in ('calw', 'cal5w'):
|
|
641
691
|
d = (startWeekDateTime +
|
|
642
692
|
timedelta(days=j)).strftime("%d %b")
|
|
643
693
|
else: # (cmd == 'calm'):
|
|
@@ -648,9 +698,11 @@ class IcalendarInterface:
|
|
|
648
698
|
d = ''
|
|
649
699
|
tmpDateColor = self.options['color_date']
|
|
650
700
|
|
|
651
|
-
if (
|
|
652
|
-
(
|
|
653
|
-
|
|
701
|
+
if (
|
|
702
|
+
self.now.strftime("%d%b%Y") == (
|
|
703
|
+
(startWeekDateTime + timedelta(days=j))
|
|
704
|
+
.strftime("%d%b%Y"))
|
|
705
|
+
):
|
|
654
706
|
tmpDateColor = self.options['color_now_marker']
|
|
655
707
|
d += " **"
|
|
656
708
|
|
|
@@ -690,7 +742,6 @@ class IcalendarInterface:
|
|
|
690
742
|
curr_event.title)
|
|
691
743
|
padding = ' ' * (self.options['cal_width']
|
|
692
744
|
- print_len)
|
|
693
|
-
|
|
694
745
|
self.printer.msg(
|
|
695
746
|
curr_event.title[:cut_idx] + padding,
|
|
696
747
|
curr_event.color)
|
|
@@ -834,9 +885,11 @@ class IcalendarInterface:
|
|
|
834
885
|
self.printer.msg(' %s' % self.valid_title(event).strip(),
|
|
835
886
|
eventColor)
|
|
836
887
|
|
|
837
|
-
if(
|
|
838
|
-
|
|
839
|
-
|
|
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(
|
|
851
|
-
|
|
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(
|
|
919
|
-
|
|
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
|
|
1162
|
-
|
|
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
|
-
|
|
1241
|
+
weeks = int(totalDays / 7)
|
|
1179
1242
|
if totalDays % 7:
|
|
1180
|
-
|
|
1243
|
+
weeks += 1
|
|
1181
1244
|
|
|
1182
1245
|
eventList = self.search_for_events(start, end, None)
|
|
1183
1246
|
|
|
1184
|
-
self.GraphEvents(cmd, start,
|
|
1247
|
+
self.GraphEvents(cmd, start, weeks, eventList)
|
|
1185
1248
|
|
|
1186
1249
|
def sync(self):
|
|
1187
1250
|
r"""Sync calendar
|
|
@@ -1401,8 +1464,10 @@ class IcalendarInterface:
|
|
|
1401
1464
|
(old.Decoded('dtend') - old.Decoded('dtstart')))
|
|
1402
1465
|
if not args.summary:
|
|
1403
1466
|
args.summary = old.Decoded('summary').decode()
|
|
1404
|
-
if
|
|
1405
|
-
|
|
1467
|
+
if (
|
|
1468
|
+
not args.time and not (args.start and 'T' in args.start)
|
|
1469
|
+
and self.isallday(old)
|
|
1470
|
+
):
|
|
1406
1471
|
args.allday = True
|
|
1407
1472
|
day = args.day or old_start.day
|
|
1408
1473
|
month = args.month or old_start.month
|
|
@@ -1690,6 +1755,10 @@ def repl(ecal=None):
|
|
|
1690
1755
|
ecal.CalQuery(
|
|
1691
1756
|
'calw', count=FLAGS.weeks, startText=FLAGS.start)
|
|
1692
1757
|
|
|
1758
|
+
elif FLAGS.command in ['5w', 'cal5w']:
|
|
1759
|
+
ecal.CalQuery(
|
|
1760
|
+
'cal5w', count=FLAGS.weeks, startText=FLAGS.start)
|
|
1761
|
+
|
|
1693
1762
|
elif FLAGS.command in ['m', 'calm']:
|
|
1694
1763
|
ecal.CalQuery('calm', startText=FLAGS.start)
|
|
1695
1764
|
|
|
@@ -1716,9 +1785,11 @@ def repl(ecal=None):
|
|
|
1716
1785
|
field=FLAGS.property)
|
|
1717
1786
|
|
|
1718
1787
|
elif FLAGS.command in ['q', 'quit']:
|
|
1719
|
-
if(
|
|
1720
|
-
|
|
1721
|
-
|
|
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
|
+
):
|
|
1722
1793
|
pass
|
|
1723
1794
|
else:
|
|
1724
1795
|
ecal.interactive = False
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: icalcli
|
|
3
|
+
Version: 1.0.6
|
|
4
|
+
Summary: Icalendar Calendar Command Line Interface
|
|
5
|
+
Home-page: https://github.com/jrvarma/icalcli
|
|
6
|
+
Maintainer: Jayanth R. Varma
|
|
7
|
+
Maintainer-email: jrvarma@gmail.com
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.6
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: python-dateutil
|
|
17
|
+
Requires-Dist: parsedatetime
|
|
18
|
+
Requires-Dist: icalendar
|
|
19
|
+
Requires-Dist: recurring_ical_events
|
|
20
|
+
Provides-Extra: parsedatetime
|
|
21
|
+
Requires-Dist: parsedatetime; extra == "parsedatetime"
|
|
22
|
+
|
|
1
23
|
# icalcli
|
|
2
24
|
|
|
3
25
|
### An iCalendar Command Line Interface
|
|
@@ -49,6 +71,7 @@ python setup.py install
|
|
|
49
71
|
edit (e) edit calendar events
|
|
50
72
|
agenda (g) get an agenda for a time period
|
|
51
73
|
calw (w) get a week-based agenda in calendar format
|
|
74
|
+
cal5w (5w) get this week / 2 past / 2 future weeks agenda in calendar format
|
|
52
75
|
calm (m) get a month agenda in calendar format
|
|
53
76
|
add (a) add a detailed event to the calendar
|
|
54
77
|
search (s) (regex) search for events
|
|
@@ -70,6 +93,15 @@ backend_interface = ICSInterface("/path/to/ics-file")
|
|
|
70
93
|
|
|
71
94
|
```
|
|
72
95
|
|
|
96
|
+
#### Example configuration for multiple readonly file_backend
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
from icalcli import ICSInterface
|
|
100
|
+
|
|
101
|
+
backend_interface = ICSInterface(["/path/to/ics-file-1", "/path/to/ics-file-2"])
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
73
105
|
#### Example configuration for etesync_backend
|
|
74
106
|
|
|
75
107
|
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 +121,16 @@ backend_interface = EtesyncInterface(
|
|
|
89
121
|
```
|
|
90
122
|
See the [Example code](https://github.com/jrvarma/icalcli/issues/1#issuecomment-979851222) for getting the `uid` and `authToken` for the `etesync` calendar.
|
|
91
123
|
|
|
124
|
+
## Recurring events and default search period
|
|
125
|
+
|
|
126
|
+
`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.
|
|
127
|
+
|
|
128
|
+
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`
|
|
129
|
+
|
|
130
|
+
## Raw ICS
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
92
134
|
## Screenshots
|
|
93
135
|
|
|
94
136
|
Some screenshots are available at Github:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|