calendar-assistant 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +176 -102
- data/Rakefile +35 -7
- data/lib/calendar_assistant.rb +1 -0
- data/lib/calendar_assistant/calendar_assistant.rb +10 -7
- data/lib/calendar_assistant/cli.rb +1 -0
- data/lib/calendar_assistant/cli/command_service.rb +37 -0
- data/lib/calendar_assistant/cli/commands.rb +52 -52
- data/lib/calendar_assistant/config.rb +43 -11
- data/lib/calendar_assistant/event.rb +50 -7
- data/lib/calendar_assistant/event_repository.rb +15 -6
- data/lib/calendar_assistant/predicate_collection.rb +21 -0
- data/lib/calendar_assistant/scheduler.rb +2 -2
- data/lib/calendar_assistant/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d3530bae8b1e8aac4260f546dd2fb2c3673b09c57d1c98e5f9e91bf91bdf5ed
|
4
|
+
data.tar.gz: e7c80eca297a7aa6ce37abfae434507368c54764c1af4d4175a346e7a4cbf9f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7dcd5d6d01422c2ee042f47f0fba9fad1886fa3b2d2d2b65c411289007d5b20d3c262b888d325036c1f4bb79bf4c4d405e47c206f467adcf1a02de77be26afc
|
7
|
+
data.tar.gz: 34a1c905b4c45e5562639001721eacc977a5c3511f8527b986b9de294249e590a0e8ec0cebe689d22d2ffb0a6066f4565532d729a22491240a25d6ee6c4425a6
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
|
1
|
+
<img src="icons/calendar-assistant.png" align="right" />
|
2
2
|
|
3
|
-
|
3
|
+
# Calendar Assistant
|
4
|
+
>A command-line tool to help you manage your Google Calendar.
|
4
5
|
|
5
6
|
- easily join the videoconference for your current meeting
|
6
7
|
- see yours and others' "availability" suitable for an email response
|
@@ -20,6 +21,7 @@ A command-line tool to help you manage your Google Calendar.
|
|
20
21
|
* [Pretty Display in Your Terminal](#pretty-display-in-your-terminal)
|
21
22
|
* [Human-Friendly Date and Time Specification](#human-friendly-date-and-time-specification)
|
22
23
|
* [Human-Friendly Duration Specification](#human-friendly-duration-specification)
|
24
|
+
* [Filter Events by property](#filter-events-by-property)
|
23
25
|
* [Preferences](#preferences)
|
24
26
|
- [Setup](#setup)
|
25
27
|
* [Installation](#installation)
|
@@ -66,6 +68,43 @@ Also note that every command will adopt an intelligent default, which is general
|
|
66
68
|
|
67
69
|
Some duration-related preferences are interpreted by [ChronicDuration](https://github.com/henrypoydar/chronic_duration) and so can be terms like "10m", "30 minutes", "four hours", etc.
|
68
70
|
|
71
|
+
### Filter Events by property
|
72
|
+
|
73
|
+
Commands that return events like `show`, `join`, `availability`, `location` or `lint` can be filtered using the option `--must-be` or `--must-not-be`. These options expect a comma separated list of one of the following properties.
|
74
|
+
|
75
|
+
* **Response**
|
76
|
+
* `accepted`
|
77
|
+
* `declined`
|
78
|
+
* `awaiting`
|
79
|
+
* `tentative`
|
80
|
+
* **Temporal**
|
81
|
+
* `all_day`
|
82
|
+
* `past`
|
83
|
+
* `current`
|
84
|
+
* `future`
|
85
|
+
* **Visibility**
|
86
|
+
* `private`
|
87
|
+
* `public`
|
88
|
+
* `explicitly_visible`
|
89
|
+
* `visible_guestlist`
|
90
|
+
* **Attributes**
|
91
|
+
* `location_event`
|
92
|
+
* `self`
|
93
|
+
* `one_on_one`
|
94
|
+
* `busy`
|
95
|
+
* `commitment`
|
96
|
+
* `recurring`
|
97
|
+
* `abandoned`
|
98
|
+
* `anyone_can_add_self`
|
99
|
+
* `attendees_omitted`
|
100
|
+
* `end_time_unspecified`
|
101
|
+
* `guests_can_invite_others`
|
102
|
+
* `guests_can_modify`
|
103
|
+
* `guests_can_see_other_guests`
|
104
|
+
* `private_copy`
|
105
|
+
* `locked`
|
106
|
+
* `needs_action`
|
107
|
+
|
69
108
|
|
70
109
|
### Preferences
|
71
110
|
|
@@ -73,10 +112,32 @@ All tokens and preferences will be stored in `~/.calendar-assistant` which is in
|
|
73
112
|
|
74
113
|
The location of `.calendar-assistant` defaults to the user's home directory. This location may be overridden by setting the environment variable `CA_HOME`
|
75
114
|
|
115
|
+
|
116
|
+
#### Nickname
|
117
|
+
|
118
|
+
Some commands, like `location-set`, will refer to you by nickname if you configure it (the alternative might be to not specify your name, or use your email id).
|
119
|
+
|
120
|
+
Set `nickname` to a string that would uniquely and briefly identify you to others, like "Mike D" or "JK".
|
121
|
+
|
122
|
+
|
76
123
|
#### Location Emoji
|
77
124
|
|
78
125
|
There is a `[settings]` key called `location-icons` that may be set to an array denoting all emoji that indicate a location event. The first icon in the array will be used as the default. The default emoji included are `["🗺 ", "🌎"]` with the map icon first
|
79
126
|
|
127
|
+
|
128
|
+
#### Command-Specific Preferences
|
129
|
+
|
130
|
+
If there are user preferences you'd like to set for just a single command (e.g., making public location events created via `location-set`), you can create a nested section in your TOML settings file, like so:
|
131
|
+
|
132
|
+
```toml
|
133
|
+
[settings]
|
134
|
+
visibility = default
|
135
|
+
|
136
|
+
[settings.location_set]
|
137
|
+
visibility = public
|
138
|
+
```
|
139
|
+
|
140
|
+
|
80
141
|
## Setup
|
81
142
|
|
82
143
|
### Installation
|
@@ -91,10 +152,10 @@ Usage:
|
|
91
152
|
calendar-assistant setup
|
92
153
|
|
93
154
|
Options:
|
94
|
-
-h, -?, [--help], [--no-help]
|
95
|
-
[--debug], [--no-debug]
|
96
|
-
|
97
|
-
|
155
|
+
-h, -?, [--help], [--no-help]
|
156
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
157
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
158
|
+
# Default: true
|
98
159
|
|
99
160
|
Description:
|
100
161
|
This command will walk you through setting up a Google Cloud Project, enabling the Google Calendar
|
@@ -112,10 +173,10 @@ Usage:
|
|
112
173
|
calendar-assistant authorize PROFILE_NAME
|
113
174
|
|
114
175
|
Options:
|
115
|
-
-h, -?, [--help], [--no-help]
|
116
|
-
[--debug], [--no-debug]
|
117
|
-
|
118
|
-
|
176
|
+
-h, -?, [--help], [--no-help]
|
177
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
178
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
179
|
+
# Default: true
|
119
180
|
|
120
181
|
Description:
|
121
182
|
Create and authorize a named profile (e.g., "work", "home", "flastname@company.tld") to access your
|
@@ -146,10 +207,10 @@ Commands:
|
|
146
207
|
calendar-assistant version # Display the version of calend...
|
147
208
|
|
148
209
|
Options:
|
149
|
-
-h, -?, [--help], [--no-help]
|
150
|
-
[--debug], [--no-debug]
|
151
|
-
|
152
|
-
|
210
|
+
-h, -?, [--help], [--no-help]
|
211
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
212
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
213
|
+
# Default: true
|
153
214
|
</pre>
|
154
215
|
|
155
216
|
|
@@ -160,14 +221,16 @@ Usage:
|
|
160
221
|
calendar-assistant join [TIME]
|
161
222
|
|
162
223
|
Options:
|
163
|
-
[--join], [--no-join]
|
164
|
-
|
165
|
-
-p, [--profile=PROFILE]
|
166
|
-
|
167
|
-
-
|
168
|
-
|
169
|
-
|
170
|
-
|
224
|
+
[--join], [--no-join] # launch a browser to join the video call URL
|
225
|
+
# Default: true
|
226
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
227
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
228
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
229
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
230
|
+
-h, -?, [--help], [--no-help]
|
231
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
232
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
233
|
+
# Default: true
|
171
234
|
|
172
235
|
Open the URL for a video call attached to your meeting at time TIME (default 'now')
|
173
236
|
</pre>
|
@@ -201,16 +264,18 @@ Usage:
|
|
201
264
|
calendar-assistant availability [DATE | DATERANGE | TIMERANGE]
|
202
265
|
|
203
266
|
Options:
|
204
|
-
-l, [--meeting-length=LENGTH]
|
205
|
-
-s, [--start-of-day=TIME]
|
206
|
-
-e, [--end-of-day=TIME]
|
207
|
-
-a, [--attendees=ATTENDEE1[,ATTENDEE2[,...]]]
|
208
|
-
-p, [--profile=PROFILE]
|
209
|
-
|
210
|
-
-
|
211
|
-
|
212
|
-
|
213
|
-
|
267
|
+
-l, [--meeting-length=LENGTH] # [default 30m] find chunks of available time at least as long as LENGTH (which is a ChronicDuration string like '30m' or '2h')
|
268
|
+
-s, [--start-of-day=TIME] # [default 9am] find chunks of available time after TIME (which is a BusinessTime string like '9am' or '14:30')
|
269
|
+
-e, [--end-of-day=TIME] # [default 6pm] find chunks of available time before TIME (which is a BusinessTime string like '9am' or '14:30')
|
270
|
+
-a, [--attendees=ATTENDEE1[,ATTENDEE2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
271
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
272
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
273
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
274
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
275
|
+
-h, -?, [--help], [--no-help]
|
276
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
277
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
278
|
+
# Default: true
|
214
279
|
|
215
280
|
Show your availability for a date or range of dates (default 'today')
|
216
281
|
</pre>
|
@@ -302,12 +367,15 @@ Usage:
|
|
302
367
|
calendar-assistant location-set LOCATION [DATE | DATERANGE]
|
303
368
|
|
304
369
|
Options:
|
305
|
-
|
306
|
-
|
307
|
-
-
|
308
|
-
|
309
|
-
|
310
|
-
|
370
|
+
[--visibility=VISIBILITY] # [default is 'default'] Set the visbility of the event. Values are 'public', 'private', 'default'.
|
371
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
372
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
373
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
374
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
375
|
+
-h, -?, [--help], [--no-help]
|
376
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
377
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
378
|
+
# Default: true
|
311
379
|
|
312
380
|
Set your location to LOCATION for a date or range of dates (default 'today')
|
313
381
|
</pre>
|
@@ -346,12 +414,14 @@ Usage:
|
|
346
414
|
calendar-assistant location [DATE | DATERANGE]
|
347
415
|
|
348
416
|
Options:
|
349
|
-
-p, [--profile=PROFILE]
|
350
|
-
|
351
|
-
-
|
352
|
-
|
353
|
-
|
354
|
-
|
417
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
418
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
419
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
420
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
421
|
+
-h, -?, [--help], [--no-help]
|
422
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
423
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
424
|
+
# Default: true
|
355
425
|
|
356
426
|
Show your location for a date or range of dates (default 'today')
|
357
427
|
</pre>
|
@@ -362,8 +432,8 @@ For example:
|
|
362
432
|
<b>$</b> calendar-assistant location "2018-09-24...2018-09-28"
|
363
433
|
<i>me@example.com (all times in America/New_York)
|
364
434
|
</i>
|
365
|
-
2018-09-24 - 2018-09-28 <b> |
|
366
|
-
2018-09-28 <b> |
|
435
|
+
2018-09-24 - 2018-09-28 <b> | 🌎 Iron Hills</b><i> (not-busy)</i>
|
436
|
+
2018-09-28 <b> | 🌎 Country Round</b><i> (not-busy)</i>
|
367
437
|
</pre>
|
368
438
|
|
369
439
|
|
@@ -374,14 +444,16 @@ Usage:
|
|
374
444
|
calendar-assistant show [DATE | DATERANGE | TIMERANGE]
|
375
445
|
|
376
446
|
Options:
|
377
|
-
-c, [--commitments], [--no-commitments]
|
378
|
-
-p, [--profile=PROFILE]
|
379
|
-
|
380
|
-
-a, [--attendees=ATTENDEE1[,ATTENDEE2[,...]]]
|
381
|
-
-
|
382
|
-
|
383
|
-
|
384
|
-
|
447
|
+
-c, [--commitments], [--no-commitments] # only show events that you've accepted with another person
|
448
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
449
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
450
|
+
-a, [--attendees=ATTENDEE1[,ATTENDEE2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
451
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
452
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
453
|
+
-h, -?, [--help], [--no-help]
|
454
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
455
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
456
|
+
# Default: true
|
385
457
|
|
386
458
|
Show your events for a date or range of dates (default 'today')
|
387
459
|
</pre>
|
@@ -392,28 +464,28 @@ For example: display all events scheduled for tomorrow:
|
|
392
464
|
<b>$</b> calendar-assistant show 2018-10-01
|
393
465
|
<i>me@example.com (all times in America/New_York)
|
394
466
|
</i>
|
395
|
-
<strike>2018-10-01 03:30 - 05:00 |
|
396
|
-
<strike>2018-10-01 07:30 - 08:30 |
|
397
|
-
<strike>2018-10-01 07:30 - 08:30 |
|
398
|
-
2018-10-01 10:30 - 10:55<b> |
|
399
|
-
2018-10-01 11:00 - 11:30<b> |
|
400
|
-
2018-10-01 11:30 - 12:00<b> |
|
401
|
-
2018-10-01 11:50 - 12:00<b> |
|
402
|
-
2018-10-01 12:00 - 12:30<b> |
|
403
|
-
<strike>2018-10-01 12:15 - 12:30 |
|
404
|
-
<strike>2018-10-01 12:30 - 13:30 |
|
405
|
-
2018-10-01 12:30 - 13:30<b> |
|
406
|
-
2018-10-01 13:30 - 14:50<b> |
|
407
|
-
<strike>2018-10-01 13:30 - 14:30 | Enable
|
408
|
-
2018-10-01 15:00 - 15:30<b> |
|
409
|
-
2018-10-01 16:00 - 17:00<b> |
|
410
|
-
2018-10-01 16:45 - 17:00<b> |
|
411
|
-
2018-10-01 17:00 - 17:30<b> |
|
412
|
-
2018-10-01 17:30 - 17:55<b> |
|
413
|
-
<strike>2018-10-01 18:00 - 20:30 |
|
414
|
-
<strike>2018-10-01 18:30 - 19:00 |
|
415
|
-
2018-10-01 19:00 - 19:30<b> |
|
416
|
-
2018-10-01 <b> |
|
467
|
+
<strike>2018-10-01 03:30 - 05:00 | Facilitate 24/365 web services</strike>
|
468
|
+
<strike>2018-10-01 07:30 - 08:30 | Reintermediate magnetic communities</strike>
|
469
|
+
<strike>2018-10-01 07:30 - 08:30 | Revolutionize bricks-and-clicks synergies</strike>
|
470
|
+
2018-10-01 10:30 - 10:55<b> | Whiteboard clicks-and-mortar metrics</b><i> (1:1, recurring)</i>
|
471
|
+
2018-10-01 11:00 - 11:30<b> | Architect holistic models</b><i> (recurring)</i>
|
472
|
+
2018-10-01 11:30 - 12:00<b> | Reinvent bleeding-edge e-services</b><i> (1:1, recurring)</i>
|
473
|
+
2018-10-01 11:50 - 12:00<b> | Synergize e-business systems</b><i> (awaiting)</i>
|
474
|
+
2018-10-01 12:00 - 12:30<b> | Reinvent e-business e-tailers</b><i> (self)</i>
|
475
|
+
<strike>2018-10-01 12:15 - 12:30 | Maximize clicks-and-mortar convergence</strike>
|
476
|
+
<strike>2018-10-01 12:30 - 13:30 | Disintermediate end-to-end bandwidth</strike>
|
477
|
+
2018-10-01 12:30 - 13:30<b> | Iterate innovative networks</b><i> (awaiting, recurring)</i>
|
478
|
+
2018-10-01 13:30 - 14:50<b> | Engage holistic users</b><i> (self)</i>
|
479
|
+
<strike>2018-10-01 13:30 - 14:30 | Enable e-business e-services</strike>
|
480
|
+
2018-10-01 15:00 - 15:30<b> | Innovate impactful technologies</b><i> (1:1)</i>
|
481
|
+
2018-10-01 16:00 - 17:00<b> | Iterate dynamic networks</b><i> (1:1, recurring)</i>
|
482
|
+
2018-10-01 16:45 - 17:00<b> | Deploy robust content</b><i> (recurring)</i>
|
483
|
+
2018-10-01 17:00 - 17:30<b> | Synthesize viral methodologies</b><i> (recurring)</i>
|
484
|
+
2018-10-01 17:30 - 17:55<b> | Scale collaborative methodologies</b><i> (1:1, recurring)</i>
|
485
|
+
<strike>2018-10-01 18:00 - 20:30 | Seize rich experiences</strike>
|
486
|
+
<strike>2018-10-01 18:30 - 19:00 | Engineer one-to-one e-business</strike>
|
487
|
+
2018-10-01 19:00 - 19:30<b> | Recontextualize collaborative content</b><i> (awaiting)</i>
|
488
|
+
2018-10-01 <b> | 🌎 Grey Mountains</b><i> (not-busy)</i>
|
417
489
|
</pre>
|
418
490
|
|
419
491
|
Display _only_ the commitments I have to other people using the `-c` option:
|
@@ -422,17 +494,17 @@ Display _only_ the commitments I have to other people using the `-c` option:
|
|
422
494
|
<b>$</b> calendar-assistant show -c 2018-10-01
|
423
495
|
<i>me@example.com (all times in America/New_York)
|
424
496
|
</i>
|
425
|
-
2018-10-01 10:30 - 10:55<b> |
|
426
|
-
2018-10-01 11:00 - 11:30<b> |
|
427
|
-
2018-10-01 11:30 - 12:00<b> |
|
428
|
-
2018-10-01 11:50 - 12:00<b> |
|
429
|
-
2018-10-01 12:30 - 13:30<b> |
|
430
|
-
2018-10-01 15:00 - 15:30<b> |
|
431
|
-
2018-10-01 16:00 - 17:00<b> |
|
432
|
-
2018-10-01 16:45 - 17:00<b> |
|
433
|
-
2018-10-01 17:00 - 17:30<b> |
|
434
|
-
2018-10-01 17:30 - 17:55<b> |
|
435
|
-
2018-10-01 19:00 - 19:30<b> |
|
497
|
+
2018-10-01 10:30 - 10:55<b> | Whiteboard clicks-and-mortar metrics</b><i> (1:1, recurring)</i>
|
498
|
+
2018-10-01 11:00 - 11:30<b> | Architect holistic models</b><i> (recurring)</i>
|
499
|
+
2018-10-01 11:30 - 12:00<b> | Reinvent bleeding-edge e-services</b><i> (1:1, recurring)</i>
|
500
|
+
2018-10-01 11:50 - 12:00<b> | Synergize e-business systems</b><i> (awaiting)</i>
|
501
|
+
2018-10-01 12:30 - 13:30<b> | Iterate innovative networks</b><i> (awaiting, recurring)</i>
|
502
|
+
2018-10-01 15:00 - 15:30<b> | Innovate impactful technologies</b><i> (1:1)</i>
|
503
|
+
2018-10-01 16:00 - 17:00<b> | Iterate dynamic networks</b><i> (1:1, recurring)</i>
|
504
|
+
2018-10-01 16:45 - 17:00<b> | Deploy robust content</b><i> (recurring)</i>
|
505
|
+
2018-10-01 17:00 - 17:30<b> | Synthesize viral methodologies</b><i> (recurring)</i>
|
506
|
+
2018-10-01 17:30 - 17:55<b> | Scale collaborative methodologies</b><i> (1:1, recurring)</i>
|
507
|
+
2018-10-01 19:00 - 19:30<b> | Recontextualize collaborative content</b><i> (awaiting)</i>
|
436
508
|
</pre>
|
437
509
|
|
438
510
|
|
@@ -443,13 +515,15 @@ Usage:
|
|
443
515
|
calendar-assistant lint [DATE | DATERANGE | TIMERANGE]
|
444
516
|
|
445
517
|
Options:
|
446
|
-
-p, [--profile=PROFILE]
|
447
|
-
|
448
|
-
-a, [--attendees=ATTENDEE1[,ATTENDEE2[,...]]]
|
449
|
-
-
|
450
|
-
|
451
|
-
|
452
|
-
|
518
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
519
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
520
|
+
-a, [--attendees=ATTENDEE1[,ATTENDEE2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
521
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
522
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
523
|
+
-h, -?, [--help], [--no-help]
|
524
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
525
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
526
|
+
# Default: true
|
453
527
|
|
454
528
|
Lint your events for a date or range of dates (default 'today')
|
455
529
|
</pre>
|
@@ -462,11 +536,11 @@ me@example.com
|
|
462
536
|
- looking for events that need attention
|
463
537
|
- all times in America/New_York
|
464
538
|
|
465
|
-
2018-10-01 11:50 - 12:00<b> |
|
539
|
+
2018-10-01 11:50 - 12:00<b> | Synergize e-business systems</b><i> (awaiting)</i>
|
466
540
|
attendees: 👍 three@example.com, 🤷 four@example.com
|
467
|
-
2018-10-01 12:30 - 13:30<b> |
|
541
|
+
2018-10-01 12:30 - 13:30<b> | Iterate innovative networks</b><i> (awaiting, recurring)</i>
|
468
542
|
attendees: 👍 three@example.com, 🤷 four@example.com
|
469
|
-
2018-10-01 19:00 - 19:30<b> |
|
543
|
+
2018-10-01 19:00 - 19:30<b> | Recontextualize collaborative content</b><i> (awaiting)</i>
|
470
544
|
attendees: 👍 three@example.com, 🤷 four@example.com
|
471
545
|
</pre>
|
472
546
|
|
@@ -480,10 +554,10 @@ Usage:
|
|
480
554
|
calendar-assistant config
|
481
555
|
|
482
556
|
Options:
|
483
|
-
-h, -?, [--help], [--no-help]
|
484
|
-
[--debug], [--no-debug]
|
485
|
-
|
486
|
-
|
557
|
+
-h, -?, [--help], [--no-help]
|
558
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
559
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
560
|
+
# Default: true
|
487
561
|
|
488
562
|
Dump your configuration parameters (merge of defaults and overrides from /home/user/.calendar-assistant)
|
489
563
|
</pre>
|
@@ -495,7 +569,7 @@ The output is TOML, which is suitable for dumping into `~/.calendar-assistant` a
|
|
495
569
|
|
496
570
|
[settings]
|
497
571
|
end-of-day = "6pm"
|
498
|
-
location-
|
572
|
+
location-icon = "🌎"
|
499
573
|
meeting-length = "30m"
|
500
574
|
profile = "work"
|
501
575
|
start-of-day = "9am"
|
data/Rakefile
CHANGED
@@ -3,25 +3,53 @@ require "rspec/core/rake_task"
|
|
3
3
|
require "concourse"
|
4
4
|
require "license_finder"
|
5
5
|
|
6
|
-
|
6
|
+
#
|
7
|
+
# concourse
|
8
|
+
#
|
9
|
+
Concourse.new("calendar-assistant", fly_target: "calendar-assistants").create_tasks!
|
7
10
|
|
8
|
-
RSpec::Core::RakeTask.new(:test)
|
9
11
|
|
10
|
-
|
12
|
+
#
|
13
|
+
# spec tasks
|
14
|
+
#
|
15
|
+
desc "Run unit and feature specs"
|
16
|
+
RSpec::Core::RakeTask.new("spec")
|
17
|
+
task "test" => "spec" # undocumented alias for 'spec'
|
18
|
+
|
19
|
+
desc "Run unit specs"
|
20
|
+
RSpec::Core::RakeTask.new("spec:unit") do |t|
|
11
21
|
t.rspec_opts ||= []
|
12
22
|
t.rspec_opts << " --tag=~type:aruba"
|
13
23
|
end
|
14
24
|
|
15
|
-
|
25
|
+
desc "Run feature specs"
|
26
|
+
RSpec::Core::RakeTask.new("spec:feature") do |t|
|
16
27
|
t.rspec_opts ||= []
|
17
28
|
t.rspec_opts << " --tag=type:aruba"
|
18
29
|
end
|
19
30
|
|
20
31
|
desc "Ensure all dependencies meet license requirements."
|
21
|
-
task
|
32
|
+
task "license_finder" do
|
22
33
|
LicenseFinder::CLI::Main.start(["report"])
|
23
34
|
LicenseFinder::CLI::Main.start([])
|
24
35
|
end
|
25
36
|
|
26
|
-
desc "Run specs,
|
27
|
-
task
|
37
|
+
desc "Run unit specs, feature specs, and license finder"
|
38
|
+
task "default" => ["spec", "license_finder"]
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
# docker docker docker
|
43
|
+
#
|
44
|
+
desc "Build a docker image for testing"
|
45
|
+
task "docker:build" do
|
46
|
+
sh "docker build -t flavorjones/calendar-assistant-test -f concourse/images/Dockerfile ."
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Push a docker image for testing"
|
50
|
+
task "docker:push" do
|
51
|
+
sh "docker push flavorjones/calendar-assistant-test"
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Build and push a docker image for testing"
|
55
|
+
task "docker" => ["docker:build", "docker:push"]
|
data/lib/calendar_assistant.rb
CHANGED
@@ -35,6 +35,7 @@ class CalendarAssistant
|
|
35
35
|
autoload :EventSet, "calendar_assistant/event_set"
|
36
36
|
autoload :Scheduler, "calendar_assistant/scheduler"
|
37
37
|
autoload :LocalService, "calendar_assistant/local_service"
|
38
|
+
autoload :PredicateCollection, "calendar_assistant/predicate_collection"
|
38
39
|
end
|
39
40
|
|
40
41
|
require "calendar_assistant/cli"
|
@@ -37,8 +37,7 @@ class CalendarAssistant
|
|
37
37
|
@calendar = service.get_calendar Config::DEFAULT_CALENDAR_ID
|
38
38
|
@event_repository_factory = event_repository_factory
|
39
39
|
@event_repositories = {} # calendar_id → event_repository
|
40
|
-
|
41
|
-
@emoji_worldmap = Array(@config.setting(CalendarAssistant::Config::Keys::Settings::LOCATION_ICONS)).first
|
40
|
+
@event_predicates = PredicateCollection.build(config.must_be, config.must_not_be)
|
42
41
|
end
|
43
42
|
|
44
43
|
def in_env &block
|
@@ -62,7 +61,7 @@ class CalendarAssistant
|
|
62
61
|
raise BaseException, "CalendarAssistant#lint_events only supports one person (for now)"
|
63
62
|
end
|
64
63
|
|
65
|
-
event_repository(calendar_ids.first).find(time_range, predicates: {
|
64
|
+
event_repository(calendar_ids.first).find(time_range, predicates: @event_predicates.merge({needs_action?: true}))
|
66
65
|
end
|
67
66
|
|
68
67
|
def find_events time_range
|
@@ -70,7 +69,7 @@ class CalendarAssistant
|
|
70
69
|
if calendar_ids.length > 1
|
71
70
|
raise BaseException, "CalendarAssistant#find_events only supports one person (for now)"
|
72
71
|
end
|
73
|
-
event_repository(calendar_ids.first).find(time_range)
|
72
|
+
event_repository(calendar_ids.first).find(time_range, predicates: @event_predicates)
|
74
73
|
end
|
75
74
|
|
76
75
|
def availability time_range
|
@@ -78,11 +77,11 @@ class CalendarAssistant
|
|
78
77
|
ers = calendar_ids.map do |calendar_id|
|
79
78
|
event_repository calendar_id
|
80
79
|
end
|
81
|
-
Scheduler.new(self, ers).available_blocks(time_range)
|
80
|
+
Scheduler.new(self, ers).available_blocks(time_range, predicates: @event_predicates)
|
82
81
|
end
|
83
82
|
|
84
83
|
def find_location_events time_range
|
85
|
-
event_set = event_repository.find(time_range)
|
84
|
+
event_set = event_repository.find(time_range, predicates: @event_predicates)
|
86
85
|
event_set.new event_set.events.select { |e| e.location_event? }
|
87
86
|
end
|
88
87
|
|
@@ -96,7 +95,11 @@ class CalendarAssistant
|
|
96
95
|
deleted_events = []
|
97
96
|
modified_events = []
|
98
97
|
|
99
|
-
event = event_repository.create(
|
98
|
+
event = event_repository.create(
|
99
|
+
transparency: CalendarAssistant::Event::Transparency::TRANSPARENT,
|
100
|
+
start: range.first, end: range.last,
|
101
|
+
summary: "#{Event.location_event_prefix(@config)}#{location}"
|
102
|
+
)
|
100
103
|
|
101
104
|
existing_event_set.events.each do |existing_event|
|
102
105
|
if existing_event.start_date >= event.start_date && existing_event.end_date <= event.end_date
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
module CLI
|
3
|
+
class CommandService
|
4
|
+
attr_reader :options, :config, :out
|
5
|
+
|
6
|
+
def initialize(context:, options: {})
|
7
|
+
@options = options.dup
|
8
|
+
@options[CalendarAssistant::Config::Keys::Options::CONTEXT] ||= context.to_s
|
9
|
+
|
10
|
+
@config = CalendarAssistant::CLI::Config.new(options: @options)
|
11
|
+
@authorizer = {}
|
12
|
+
@out = CalendarAssistant::CLI::Printer.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def calendar_assistant(datespec)
|
16
|
+
ca = CalendarAssistant.new(config, service: service)
|
17
|
+
ca.in_env do
|
18
|
+
yield(ca, CalendarAssistant::CLI::Helpers.parse_datespec(datespec), out)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def authorizer(profile_name: config.profile_name, token_store: config.token_store)
|
23
|
+
@authorizer[profile_name] ||= Authorizer.new(profile_name, token_store)
|
24
|
+
end
|
25
|
+
|
26
|
+
def service
|
27
|
+
@service ||= begin
|
28
|
+
if filename = config.setting(Config::Keys::Options::LOCAL_STORE)
|
29
|
+
CalendarAssistant::LocalService.new(file: filename)
|
30
|
+
else
|
31
|
+
authorizer.service
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "calendar_assistant/cli/helpers"
|
2
|
-
|
3
1
|
class CalendarAssistant
|
4
2
|
module CLI
|
5
3
|
class Commands < Thor
|
@@ -12,7 +10,21 @@ class CalendarAssistant
|
|
12
10
|
option CalendarAssistant::Config::Keys::Options::LOCAL_STORE,
|
13
11
|
type: :string,
|
14
12
|
banner: "FILENAME",
|
15
|
-
desc: "Load events from a local file instead of Google Calendar"
|
13
|
+
desc: "Load events from a local file instead of Google Calendar",
|
14
|
+
aliases: [ "-l" ]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.has_events
|
18
|
+
option CalendarAssistant::Config::Keys::Options::MUST_BE,
|
19
|
+
type: :string,
|
20
|
+
desc: "Event properties that must be true (see README)",
|
21
|
+
banner: "PROPERTY1[,PROPERTY2[,...]]",
|
22
|
+
aliases: [ "-b" ]
|
23
|
+
option CalendarAssistant::Config::Keys::Options::MUST_NOT_BE,
|
24
|
+
type: :string,
|
25
|
+
desc: "Event properties that must be false (see README)",
|
26
|
+
banner: "PROPERTY1[,PROPERTY2[,...]]",
|
27
|
+
aliases: [ "-n" ]
|
16
28
|
end
|
17
29
|
|
18
30
|
def self.has_attendees
|
@@ -35,14 +47,16 @@ class CalendarAssistant
|
|
35
47
|
class_option CalendarAssistant::Config::Keys::Options::FORMATTING,
|
36
48
|
type: :boolean,
|
37
49
|
desc: "Enable Text Formatting",
|
38
|
-
default: CalendarAssistant::Config::DEFAULT_SETTINGS[CalendarAssistant::Config::Keys::Options::FORMATTING]
|
50
|
+
default: CalendarAssistant::Config::DEFAULT_SETTINGS[CalendarAssistant::Config::Keys::Options::FORMATTING],
|
51
|
+
aliases: "-f"
|
52
|
+
|
39
53
|
|
40
54
|
desc "version",
|
41
55
|
"Display the version of calendar-assistant"
|
42
56
|
|
43
57
|
def version
|
44
58
|
return if handle_help_args
|
45
|
-
out.puts CalendarAssistant::VERSION
|
59
|
+
command_service.out.puts CalendarAssistant::VERSION
|
46
60
|
end
|
47
61
|
|
48
62
|
|
@@ -52,7 +66,7 @@ class CalendarAssistant
|
|
52
66
|
def config
|
53
67
|
return if handle_help_args
|
54
68
|
settings = CalendarAssistant::CLI::Config.new.settings
|
55
|
-
out.puts TOML::Generator.new({CalendarAssistant::Config::Keys::SETTINGS => settings}).body
|
69
|
+
command_service.out.puts TOML::Generator.new({CalendarAssistant::Config::Keys::SETTINGS => settings}).body
|
56
70
|
end
|
57
71
|
|
58
72
|
|
@@ -72,14 +86,14 @@ class CalendarAssistant
|
|
72
86
|
# TODO ugh see #34 for advice on how to clean this up
|
73
87
|
return if handle_help_args
|
74
88
|
if File.exist? CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH
|
75
|
-
out.puts sprintf("Credentials already exist in %s",
|
76
|
-
|
89
|
+
command_service.out.puts sprintf("Credentials already exist in %s",
|
90
|
+
CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH)
|
77
91
|
return
|
78
92
|
end
|
79
93
|
|
80
|
-
out.launch "https://developers.google.com/calendar/quickstart/ruby"
|
94
|
+
command_service.out.launch "https://developers.google.com/calendar/quickstart/ruby"
|
81
95
|
sleep 1
|
82
|
-
out.puts <<~EOT
|
96
|
+
command_service.out.puts <<~EOT
|
83
97
|
Please click on "ENABLE THE GOOGLE CALENDAR API" and either create a new project or select an existing project.
|
84
98
|
|
85
99
|
(If you create a new project, name it something like "yourname-calendar-assistant" so you remember why it exists.)
|
@@ -89,13 +103,13 @@ class CalendarAssistant
|
|
89
103
|
Finally, paste the contents of the downloaded file here (it should be a complete JSON object):
|
90
104
|
EOT
|
91
105
|
|
92
|
-
json = out.prompt "Paste JSON here"
|
106
|
+
json = command_service.out.prompt "Paste JSON here"
|
93
107
|
File.open(CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH, "w") do |f|
|
94
108
|
f.write json
|
95
109
|
end
|
96
110
|
FileUtils.chmod 0600, CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH
|
97
111
|
|
98
|
-
out.puts "\nOK! Your next step is to run `calendar-assistant authorize`."
|
112
|
+
command_service.out.puts "\nOK! Your next step is to run `calendar-assistant authorize`."
|
99
113
|
end
|
100
114
|
|
101
115
|
|
@@ -117,7 +131,7 @@ class CalendarAssistant
|
|
117
131
|
return if handle_help_args
|
118
132
|
return help! if profile_name.nil?
|
119
133
|
|
120
|
-
|
134
|
+
command_service.authorizer(profile_name: profile_name).authorize
|
121
135
|
|
122
136
|
puts "\nYou're authorized!\n\n"
|
123
137
|
end
|
@@ -127,13 +141,14 @@ class CalendarAssistant
|
|
127
141
|
will_create_a_service
|
128
142
|
has_attendees
|
129
143
|
|
144
|
+
has_events
|
130
145
|
def lint datespec = "today"
|
131
|
-
calendar_assistant(datespec) do |ca, date|
|
146
|
+
calendar_assistant(datespec) do |ca, date, out|
|
132
147
|
event_set = ca.lint_events date
|
133
148
|
out.print_events ca, event_set, presenter_class: CalendarAssistant::CLI::LinterEventSetPresenter
|
134
149
|
end
|
135
150
|
end
|
136
|
-
|
151
|
+
|
137
152
|
desc "show [DATE | DATERANGE | TIMERANGE]",
|
138
153
|
"Show your events for a date or range of dates (default 'today')"
|
139
154
|
option CalendarAssistant::Config::Keys::Options::COMMITMENTS,
|
@@ -143,8 +158,9 @@ class CalendarAssistant
|
|
143
158
|
will_create_a_service
|
144
159
|
has_attendees
|
145
160
|
|
161
|
+
has_events
|
146
162
|
def show datespec = "today"
|
147
|
-
calendar_assistant(datespec) do |ca, date|
|
163
|
+
calendar_assistant(datespec) do |ca, date, out|
|
148
164
|
event_set = ca.find_events date
|
149
165
|
out.print_events ca, event_set
|
150
166
|
end
|
@@ -158,18 +174,19 @@ class CalendarAssistant
|
|
158
174
|
desc: "launch a browser to join the video call URL"
|
159
175
|
will_create_a_service
|
160
176
|
|
177
|
+
has_events
|
161
178
|
def join timespec = "now"
|
162
179
|
return if handle_help_args
|
163
180
|
set_formatting
|
164
|
-
ca = CalendarAssistant.new
|
181
|
+
ca = CalendarAssistant.new command_service.config, service: command_service.service
|
165
182
|
ca.in_env do
|
166
183
|
event_set, url = CalendarAssistant::CLI::Helpers.find_av_uri ca, timespec
|
167
184
|
if !event_set.empty?
|
168
|
-
out.print_events ca, event_set
|
169
|
-
out.puts url
|
170
|
-
out.launch url if options[CalendarAssistant::Config::Keys::Options::JOIN]
|
185
|
+
command_service.out.print_events ca, event_set
|
186
|
+
command_service.out.puts url
|
187
|
+
command_service.out.launch url if options[CalendarAssistant::Config::Keys::Options::JOIN]
|
171
188
|
else
|
172
|
-
out.puts "Could not find a meeting '#{timespec}' with a video call to join."
|
189
|
+
command_service.out.puts "Could not find a meeting '#{timespec}' with a video call to join."
|
173
190
|
end
|
174
191
|
end
|
175
192
|
end
|
@@ -179,8 +196,9 @@ class CalendarAssistant
|
|
179
196
|
"Show your location for a date or range of dates (default 'today')"
|
180
197
|
will_create_a_service
|
181
198
|
|
199
|
+
has_events
|
182
200
|
def location datespec = "today"
|
183
|
-
calendar_assistant(datespec) do |ca, date|
|
201
|
+
calendar_assistant(datespec) do |ca, date, out|
|
184
202
|
event_set = ca.find_location_events date
|
185
203
|
out.print_events ca, event_set
|
186
204
|
end
|
@@ -189,12 +207,16 @@ class CalendarAssistant
|
|
189
207
|
|
190
208
|
desc "location-set LOCATION [DATE | DATERANGE]",
|
191
209
|
"Set your location to LOCATION for a date or range of dates (default 'today')"
|
210
|
+
option CalendarAssistant::Config::Keys::Settings::VISIBILITY,
|
211
|
+
type: :string,
|
212
|
+
banner: "VISIBILITY",
|
213
|
+
desc: "[default is 'default'] Set the visbility of the event. Values are 'public', 'private', 'default'."
|
192
214
|
will_create_a_service
|
193
|
-
|
215
|
+
has_events
|
194
216
|
def location_set location = nil, datespec = "today"
|
195
217
|
return help! if location.nil?
|
196
218
|
|
197
|
-
calendar_assistant(datespec) do |ca, date|
|
219
|
+
calendar_assistant(datespec) do |ca, date, out|
|
198
220
|
event_set = ca.create_location_event date, location
|
199
221
|
out.print_events ca, event_set
|
200
222
|
end
|
@@ -223,9 +245,9 @@ class CalendarAssistant
|
|
223
245
|
aliases: ["-e"]
|
224
246
|
has_attendees
|
225
247
|
will_create_a_service
|
226
|
-
|
248
|
+
has_events
|
227
249
|
def availability datespec = "today"
|
228
|
-
calendar_assistant(datespec) do |ca, date|
|
250
|
+
calendar_assistant(datespec) do |ca, date, out|
|
229
251
|
event_set = ca.availability date
|
230
252
|
out.print_available_blocks ca, event_set
|
231
253
|
end
|
@@ -237,36 +259,14 @@ class CalendarAssistant
|
|
237
259
|
Rainbow.enabled = !!options[:formatting]
|
238
260
|
end
|
239
261
|
|
240
|
-
def
|
241
|
-
@
|
242
|
-
if filename = get_config.setting(Config::Keys::Options::LOCAL_STORE)
|
243
|
-
CalendarAssistant::LocalService.new(file: filename)
|
244
|
-
else
|
245
|
-
get_authorizer.service
|
246
|
-
end
|
247
|
-
end
|
262
|
+
def command_service
|
263
|
+
@command_service ||= CommandService.new(context: current_command_chain.first, options: options)
|
248
264
|
end
|
249
265
|
|
250
|
-
def
|
251
|
-
@authorizer ||= {}
|
252
|
-
@authorizer[profile_name] ||= Authorizer.new(profile_name, token_store)
|
253
|
-
end
|
254
|
-
|
255
|
-
def calendar_assistant datespec = "today"
|
266
|
+
def calendar_assistant datespec = "today", &block
|
256
267
|
return if handle_help_args
|
257
268
|
set_formatting
|
258
|
-
|
259
|
-
ca.in_env do
|
260
|
-
yield(ca, CalendarAssistant::CLI::Helpers.parse_datespec(datespec))
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
def get_config
|
265
|
-
@config ||= CalendarAssistant::CLI::Config.new(options: options)
|
266
|
-
end
|
267
|
-
|
268
|
-
def out
|
269
|
-
@out ||= CalendarAssistant::CLI::Printer.new
|
269
|
+
command_service.calendar_assistant(datespec, &block)
|
270
270
|
end
|
271
271
|
|
272
272
|
def help!
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
class CalendarAssistant
|
2
3
|
class Config
|
3
4
|
autoload :TokenStore, "calendar_assistant/config/token_store"
|
@@ -16,11 +17,13 @@ class CalendarAssistant
|
|
16
17
|
# and which can be overridden by entries in the user config file
|
17
18
|
#
|
18
19
|
module Settings
|
19
|
-
PROFILE = "profile"
|
20
|
-
MEETING_LENGTH = "meeting-length"
|
21
|
-
START_OF_DAY = "start-of-day"
|
22
|
-
END_OF_DAY = "end-of-day"
|
23
|
-
|
20
|
+
PROFILE = "profile" # string
|
21
|
+
MEETING_LENGTH = "meeting-length" # ChronicDuration
|
22
|
+
START_OF_DAY = "start-of-day" # BusinessTime
|
23
|
+
END_OF_DAY = "end-of-day" # BusinessTime
|
24
|
+
LOCATION_ICON = "location-icon" # string emoji
|
25
|
+
NICKNAME = "nickname" # string
|
26
|
+
VISIBILITY = "visibility" # Event Visibility
|
24
27
|
end
|
25
28
|
|
26
29
|
#
|
@@ -34,13 +37,16 @@ class CalendarAssistant
|
|
34
37
|
LOCAL_STORE = "local-store" # filename
|
35
38
|
DEBUG = "debug" # bool
|
36
39
|
FORMATTING = "formatting" # Rainbow
|
40
|
+
MUST_BE = "must-be" # array of event predicates (comma-delimited)
|
41
|
+
MUST_NOT_BE = "must-not-be" # array of event predicates (comma-delimited)
|
42
|
+
CONTEXT = "context" # symbol referring to command context
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
40
46
|
DEFAULT_CALENDAR_ID = "primary"
|
41
47
|
|
42
48
|
DEFAULT_SETTINGS = {
|
43
|
-
Keys::Settings::
|
49
|
+
Keys::Settings::LOCATION_ICON => "🌎", # string emoji
|
44
50
|
Keys::Settings::MEETING_LENGTH => "30m", # ChronicDuration
|
45
51
|
Keys::Settings::START_OF_DAY => "9am", # BusinessTime
|
46
52
|
Keys::Settings::END_OF_DAY => "6pm", # BusinessTime
|
@@ -110,7 +116,9 @@ class CalendarAssistant
|
|
110
116
|
# and settings
|
111
117
|
#
|
112
118
|
def setting setting_name
|
119
|
+
context = Config.find_in_hash(options, Keys::Options::CONTEXT)
|
113
120
|
Config.find_in_hash(options, setting_name) ||
|
121
|
+
Config.find_in_hash(user_config, [Keys::SETTINGS, context, setting_name]) ||
|
114
122
|
Config.find_in_hash(user_config, [Keys::SETTINGS, setting_name]) ||
|
115
123
|
Config.find_in_hash(defaults, setting_name)
|
116
124
|
end
|
@@ -140,11 +148,15 @@ class CalendarAssistant
|
|
140
148
|
# helper method for Keys::Options::ATTENDEES
|
141
149
|
#
|
142
150
|
def attendees
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
151
|
+
split_if_array(Keys::Options::ATTENDEES)
|
152
|
+
end
|
153
|
+
|
154
|
+
def must_be
|
155
|
+
split_if_array(Keys::Options::MUST_BE)
|
156
|
+
end
|
157
|
+
|
158
|
+
def must_not_be
|
159
|
+
split_if_array(Keys::Options::MUST_NOT_BE)
|
148
160
|
end
|
149
161
|
|
150
162
|
def debug?
|
@@ -155,8 +167,28 @@ class CalendarAssistant
|
|
155
167
|
#noop
|
156
168
|
end
|
157
169
|
|
170
|
+
def event_visibility
|
171
|
+
value = setting(Keys::Settings::VISIBILITY)
|
172
|
+
case value
|
173
|
+
when CalendarAssistant::Event::Visibility::PUBLIC
|
174
|
+
CalendarAssistant::Event::Visibility::PUBLIC
|
175
|
+
when CalendarAssistant::Event::Visibility::PRIVATE
|
176
|
+
CalendarAssistant::Event::Visibility::PRIVATE
|
177
|
+
else
|
178
|
+
CalendarAssistant::Event::Visibility::DEFAULT
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
158
182
|
private
|
159
183
|
|
184
|
+
def split_if_array(key)
|
185
|
+
a = setting(key)
|
186
|
+
if a.is_a?(String)
|
187
|
+
a = a.split(",")
|
188
|
+
end
|
189
|
+
a
|
190
|
+
end
|
191
|
+
|
160
192
|
def self.find_in_hash hash, keypath
|
161
193
|
current_val = hash
|
162
194
|
keypath = keypath.split(".") unless keypath.is_a?(Array)
|
@@ -27,6 +27,45 @@ class CalendarAssistant
|
|
27
27
|
PRIVATE = "private"
|
28
28
|
end
|
29
29
|
|
30
|
+
PREDICATES = {
|
31
|
+
"response": %I[
|
32
|
+
accepted?
|
33
|
+
declined?
|
34
|
+
awaiting?
|
35
|
+
tentative?
|
36
|
+
],
|
37
|
+
"temporal": %I[
|
38
|
+
all_day?
|
39
|
+
past?
|
40
|
+
current?
|
41
|
+
future?
|
42
|
+
],
|
43
|
+
"visibility": %I[
|
44
|
+
private?
|
45
|
+
public?
|
46
|
+
explicitly_visible?
|
47
|
+
visible_guestlist?
|
48
|
+
],
|
49
|
+
"attributes": %I[
|
50
|
+
location_event?
|
51
|
+
self?
|
52
|
+
one_on_one?
|
53
|
+
busy?
|
54
|
+
commitment?
|
55
|
+
recurring?
|
56
|
+
abandoned?
|
57
|
+
anyone_can_add_self?
|
58
|
+
attendees_omitted?
|
59
|
+
end_time_unspecified?
|
60
|
+
guests_can_invite_others?
|
61
|
+
guests_can_modify?
|
62
|
+
guests_can_see_other_guests?
|
63
|
+
private_copy?
|
64
|
+
locked?
|
65
|
+
needs_action?
|
66
|
+
]
|
67
|
+
}
|
68
|
+
|
30
69
|
#
|
31
70
|
# class methods
|
32
71
|
#
|
@@ -34,17 +73,20 @@ class CalendarAssistant
|
|
34
73
|
(end_time.to_datetime - start_time.to_datetime).days.to_i
|
35
74
|
end
|
36
75
|
|
76
|
+
def self.location_event_prefix config
|
77
|
+
icon = config[CalendarAssistant::Config::Keys::Settings::LOCATION_ICON]
|
78
|
+
if nickname = config[CalendarAssistant::Config::Keys::Settings::NICKNAME]
|
79
|
+
return "#{icon} #{nickname} @ "
|
80
|
+
end
|
81
|
+
"#{icon} "
|
82
|
+
end
|
83
|
+
|
37
84
|
#
|
38
85
|
# instance methods
|
39
86
|
#
|
40
|
-
attr_reader :location_event_regex
|
41
|
-
|
42
87
|
def initialize(obj, config: CalendarAssistant::Config.new)
|
43
|
-
|
44
88
|
super(obj)
|
45
|
-
@
|
46
|
-
|
47
|
-
@location_event_regex = /^#{@location_icons.join("|")}/
|
89
|
+
@config = config
|
48
90
|
end
|
49
91
|
|
50
92
|
def update **args
|
@@ -53,7 +95,7 @@ class CalendarAssistant
|
|
53
95
|
end
|
54
96
|
|
55
97
|
def location_event?
|
56
|
-
!! (
|
98
|
+
!! summary.try(:starts_with?, Event.location_event_prefix(@config))
|
57
99
|
end
|
58
100
|
|
59
101
|
def all_day?
|
@@ -240,5 +282,6 @@ class CalendarAssistant
|
|
240
282
|
def contains? time
|
241
283
|
start_time <= time && time < end_time
|
242
284
|
end
|
285
|
+
|
243
286
|
end
|
244
287
|
end
|
@@ -28,17 +28,13 @@ class CalendarAssistant
|
|
28
28
|
)
|
29
29
|
events = events.items.map { |e| CalendarAssistant::Event.new(e, config: config) }
|
30
30
|
|
31
|
-
unless predicates.empty?
|
32
|
-
events = events.select do |event|
|
33
|
-
predicates.all? { |predicate, value| event.public_send(predicate) == value }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
31
|
+
events = filter_by_predicates(events, predicates) unless predicates.empty?
|
37
32
|
CalendarAssistant::EventSet.new self, events
|
38
33
|
end
|
39
34
|
|
40
35
|
def new event_attributes
|
41
36
|
event = Google::Apis::CalendarV3::Event.new DateHelpers.cast_dates(event_attributes)
|
37
|
+
event.visibility ||= config.event_visibility
|
42
38
|
CalendarAssistant::Event.new(event, config: config)
|
43
39
|
end
|
44
40
|
|
@@ -66,5 +62,18 @@ class CalendarAssistant
|
|
66
62
|
)
|
67
63
|
CalendarAssistant::Event.new e, config: config
|
68
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def filter_by_predicates(events, predicates)
|
69
|
+
valid_predicates = CalendarAssistant::Event::PREDICATES.values.flatten
|
70
|
+
|
71
|
+
events.select do |event|
|
72
|
+
predicates.all? do |predicate, value|
|
73
|
+
raise BaseException, "Invalid event predicate" unless valid_predicates.include?(predicate)
|
74
|
+
event.public_send(predicate) == value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
69
78
|
end
|
70
79
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
module PredicateCollection
|
3
|
+
|
4
|
+
def self.build(must_be, must_not_be)
|
5
|
+
predicates = {}
|
6
|
+
Array(must_be).each do |predicate|
|
7
|
+
predicates[predicate_symbol(predicate)] = true
|
8
|
+
end
|
9
|
+
|
10
|
+
Array(must_not_be).each do |predicate|
|
11
|
+
predicates[predicate_symbol(predicate)] = false
|
12
|
+
end
|
13
|
+
|
14
|
+
predicates
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.predicate_symbol(str)
|
18
|
+
str.to_s.gsub(/(.*[^?])$/, "\\1?").to_sym
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -26,10 +26,10 @@ class CalendarAssistant
|
|
26
26
|
@ers = Array(event_repositories)
|
27
27
|
end
|
28
28
|
|
29
|
-
def available_blocks time_range
|
29
|
+
def available_blocks time_range, predicates: {}
|
30
30
|
avail = nil
|
31
31
|
ers.each do |er|
|
32
|
-
event_set = er.find time_range # array
|
32
|
+
event_set = er.find time_range, predicates: predicates # array
|
33
33
|
event_set = Scheduler.select_busy_events event_set # hash
|
34
34
|
event_set.ensure_keys time_range.first.to_date .. time_range.last.to_date, only: true
|
35
35
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: calendar-assistant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Dalessio
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-01-
|
12
|
+
date: 2019-01-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0.24'
|
77
77
|
- - "<"
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: '0.
|
79
|
+
version: '0.29'
|
80
80
|
type: :runtime
|
81
81
|
prerelease: false
|
82
82
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -86,7 +86,7 @@ dependencies:
|
|
86
86
|
version: '0.24'
|
87
87
|
- - "<"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
89
|
+
version: '0.29'
|
90
90
|
- !ruby/object:Gem::Dependency
|
91
91
|
name: launchy
|
92
92
|
requirement: !ruby/object:Gem::Requirement
|
@@ -288,6 +288,7 @@ files:
|
|
288
288
|
- lib/calendar_assistant/calendar_assistant.rb
|
289
289
|
- lib/calendar_assistant/cli.rb
|
290
290
|
- lib/calendar_assistant/cli/authorizer.rb
|
291
|
+
- lib/calendar_assistant/cli/command_service.rb
|
291
292
|
- lib/calendar_assistant/cli/commands.rb
|
292
293
|
- lib/calendar_assistant/cli/config.rb
|
293
294
|
- lib/calendar_assistant/cli/event_presenter.rb
|
@@ -306,6 +307,7 @@ files:
|
|
306
307
|
- lib/calendar_assistant/extensions/google_apis_extensions.rb
|
307
308
|
- lib/calendar_assistant/extensions/rainbow_extensions.rb
|
308
309
|
- lib/calendar_assistant/local_service.rb
|
310
|
+
- lib/calendar_assistant/predicate_collection.rb
|
309
311
|
- lib/calendar_assistant/scheduler.rb
|
310
312
|
- lib/calendar_assistant/string_helpers.rb
|
311
313
|
- lib/calendar_assistant/version.rb
|