calendar-assistant 0.8.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/README.md +50 -46
- data/lib/calendar_assistant.rb +17 -12
- data/lib/calendar_assistant/available_block.rb +12 -0
- data/lib/calendar_assistant/calendar_assistant.rb +23 -41
- data/lib/calendar_assistant/cli/commands.rb +13 -9
- data/lib/calendar_assistant/cli/printer.rb +10 -3
- data/lib/calendar_assistant/config.rb +16 -23
- data/lib/calendar_assistant/event.rb +3 -84
- data/lib/calendar_assistant/event_repository.rb +5 -10
- data/lib/calendar_assistant/event_repository_factory.rb +11 -2
- data/lib/calendar_assistant/event_set.rb +17 -6
- data/lib/calendar_assistant/extensions/launchy_extensions.rb +44 -0
- data/lib/calendar_assistant/has_duration.rb +102 -0
- data/lib/calendar_assistant/lint_event_repository.rb +7 -0
- data/lib/calendar_assistant/location_config_validator.rb +15 -0
- data/lib/calendar_assistant/location_event_repository.rb +43 -0
- data/lib/calendar_assistant/version.rb +1 -1
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71db4f83ef4cb314c49b308e9fbf752b958823bb320aa7db5d404665e23c3eb9
|
4
|
+
data.tar.gz: 8bd30697df26b5a3975e9a06add5de2c49be64344a3787a09576d3b081bbe121
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1701eb120de9b9ebdb5065695762eeca199b043e26554386feac9eae62b9ad9cd2b2aa6abe63e7de90716c5e55b74a7fa4e80bcc0056ab29f98c4d4ff840d56
|
7
|
+
data.tar.gz: 0f5f584849f62313666931d943f0b070d0e34d3388f94df57d6ca2b7751db6c23f2ef9ed65b691980ddfda2f54a3ebbd255a356186aae25c0eab25c24abd57a8
|
data/README.md
CHANGED
@@ -9,6 +9,7 @@
|
|
9
9
|
- see views on your calendar events for a date or time range
|
10
10
|
- book (and re-book) one-on-ones and other meetings automatically
|
11
11
|
|
12
|
+
[](https://badge.fury.io/rb/calendar-assistant)
|
12
13
|
[](https://ci.nokogiri.org/teams/calendar-assistants/pipelines/calendar-assistant)
|
13
14
|
[](https://codeclimate.com/github/flavorjones/calendar-assistant/maintainability)
|
14
15
|
[](https://codeclimate.com/github/flavorjones/calendar-assistant/test_coverage)
|
@@ -119,10 +120,9 @@ Some commands, like `location-set`, will refer to you by nickname if you configu
|
|
119
120
|
|
120
121
|
Set `nickname` to a string that would uniquely and briefly identify you to others, like "Mike D" or "JK".
|
121
122
|
|
122
|
-
|
123
123
|
#### Location Emoji
|
124
124
|
|
125
|
-
There is a `[settings]` key called `location-
|
125
|
+
There is a `[settings]` key called `location-icon` that may be set to an emoji denoting a location event. By default CalendarAssistant will use `"🌎"`, but you can change this.
|
126
126
|
|
127
127
|
|
128
128
|
#### Command-Specific Preferences
|
@@ -131,10 +131,12 @@ If there are user preferences you'd like to set for just a single command (e.g.,
|
|
131
131
|
|
132
132
|
```toml
|
133
133
|
[settings]
|
134
|
-
visibility = default
|
134
|
+
visibility = "default"
|
135
|
+
nickname = "uniquely-me"
|
135
136
|
|
136
137
|
[settings.location_set]
|
137
|
-
visibility = public
|
138
|
+
visibility = "public"
|
139
|
+
calendars = ["teamcalendar@group.calendar.google.com","teamcalendar2@group.calendar.google.com"]
|
138
140
|
```
|
139
141
|
|
140
142
|
|
@@ -162,7 +164,8 @@ Description:
|
|
162
164
|
API, and saving the credentials necessary to access the API on behalf of users.
|
163
165
|
|
164
166
|
If you already have downloaded client credentials, you don't need to run this command. Instead,
|
165
|
-
rename the downloaded JSON file to
|
167
|
+
rename the downloaded JSON file to
|
168
|
+
`/home/user/.calendar-assistant.client`
|
166
169
|
</pre>
|
167
170
|
|
168
171
|
|
@@ -264,18 +267,18 @@ Usage:
|
|
264
267
|
calendar-assistant availability [DATE | DATERANGE | TIMERANGE]
|
265
268
|
|
266
269
|
Options:
|
267
|
-
-l, [--meeting-length=LENGTH]
|
268
|
-
-s, [--start-of-day=TIME]
|
269
|
-
-e, [--end-of-day=TIME]
|
270
|
-
-a, [--
|
271
|
-
-p, [--profile=PROFILE]
|
272
|
-
-l, [--local-store=FILENAME]
|
273
|
-
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]]
|
274
|
-
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]]
|
275
|
-
-h, -?, [--help], [--no-help]
|
276
|
-
[--debug], [--no-debug]
|
277
|
-
-f, [--formatting], [--no-formatting]
|
278
|
-
|
270
|
+
-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')
|
271
|
+
-s, [--start-of-day=TIME] # [default 9am] find chunks of available time after TIME (which is a BusinessTime string like '9am' or '14:30')
|
272
|
+
-e, [--end-of-day=TIME] # [default 6pm] find chunks of available time before TIME (which is a BusinessTime string like '9am' or '14:30')
|
273
|
+
-a, --attendees, [--calendars=CALENDAR1[,CALENDAR2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
274
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
275
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
276
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
277
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
278
|
+
-h, -?, [--help], [--no-help]
|
279
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
280
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
281
|
+
# Default: true
|
279
282
|
|
280
283
|
Show your availability for a date or range of dates (default 'today')
|
281
284
|
</pre>
|
@@ -367,15 +370,17 @@ Usage:
|
|
367
370
|
calendar-assistant location-set LOCATION [DATE | DATERANGE]
|
368
371
|
|
369
372
|
Options:
|
370
|
-
[--
|
371
|
-
|
372
|
-
-
|
373
|
-
-
|
374
|
-
-
|
375
|
-
-
|
376
|
-
|
377
|
-
-
|
378
|
-
|
373
|
+
[--force] # will manage location across multiple calendars whether a nickname is set or not
|
374
|
+
[--visibility=VISIBILITY] # [default is 'default'] Set the visibility of the event. Values are 'public', 'private', 'default'.
|
375
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
376
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
377
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
378
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
379
|
+
-a, --attendees, [--calendars=CALENDAR1[,CALENDAR2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
380
|
+
-h, -?, [--help], [--no-help]
|
381
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
382
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
383
|
+
# Default: true
|
379
384
|
|
380
385
|
Set your location to LOCATION for a date or range of dates (default 'today')
|
381
386
|
</pre>
|
@@ -444,16 +449,16 @@ Usage:
|
|
444
449
|
calendar-assistant show [DATE | DATERANGE | TIMERANGE]
|
445
450
|
|
446
451
|
Options:
|
447
|
-
-c, [--commitments], [--no-commitments]
|
448
|
-
-p, [--profile=PROFILE]
|
449
|
-
-l, [--local-store=FILENAME]
|
450
|
-
-a, [--
|
451
|
-
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]]
|
452
|
-
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]]
|
453
|
-
-h, -?, [--help], [--no-help]
|
454
|
-
[--debug], [--no-debug]
|
455
|
-
-f, [--formatting], [--no-formatting]
|
456
|
-
|
452
|
+
-c, [--commitments], [--no-commitments] # only show events that you've accepted with another person
|
453
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
454
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
455
|
+
-a, --attendees, [--calendars=CALENDAR1[,CALENDAR2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
456
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
457
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
458
|
+
-h, -?, [--help], [--no-help]
|
459
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
460
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
461
|
+
# Default: true
|
457
462
|
|
458
463
|
Show your events for a date or range of dates (default 'today')
|
459
464
|
</pre>
|
@@ -515,15 +520,15 @@ Usage:
|
|
515
520
|
calendar-assistant lint [DATE | DATERANGE | TIMERANGE]
|
516
521
|
|
517
522
|
Options:
|
518
|
-
-p, [--profile=PROFILE]
|
519
|
-
-l, [--local-store=FILENAME]
|
520
|
-
-a, [--
|
521
|
-
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]]
|
522
|
-
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]]
|
523
|
-
-h, -?, [--help], [--no-help]
|
524
|
-
[--debug], [--no-debug]
|
525
|
-
-f, [--formatting], [--no-formatting]
|
526
|
-
|
523
|
+
-p, [--profile=PROFILE] # the profile you'd like to use (if different from default)
|
524
|
+
-l, [--local-store=FILENAME] # Load events from a local file instead of Google Calendar
|
525
|
+
-a, --attendees, [--calendars=CALENDAR1[,CALENDAR2[,...]]] # [default 'me'] people (email IDs) to whom this command will be applied
|
526
|
+
-b, [--must-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be true (see README)
|
527
|
+
-n, [--must-not-be=PROPERTY1[,PROPERTY2[,...]]] # Event properties that must be false (see README)
|
528
|
+
-h, -?, [--help], [--no-help]
|
529
|
+
[--debug], [--no-debug] # how dare you suggest there are bugs
|
530
|
+
-f, [--formatting], [--no-formatting] # Enable Text Formatting
|
531
|
+
# Default: true
|
527
532
|
|
528
533
|
Lint your events for a date or range of dates (default 'today')
|
529
534
|
</pre>
|
@@ -571,7 +576,6 @@ The output is TOML, which is suitable for dumping into `~/.calendar-assistant` a
|
|
571
576
|
end-of-day = "6pm"
|
572
577
|
location-icon = "🌎"
|
573
578
|
meeting-length = "30m"
|
574
|
-
profile = "work"
|
575
579
|
start-of-day = "9am"
|
576
580
|
</pre>
|
577
581
|
|
data/lib/calendar_assistant.rb
CHANGED
@@ -13,7 +13,7 @@ autoload :BusinessTime, "business_time"
|
|
13
13
|
autoload :Chronic, "chronic"
|
14
14
|
autoload :ChronicDuration, "chronic_duration"
|
15
15
|
autoload :Google, "calendar_assistant/extensions/google_apis_extensions"
|
16
|
-
autoload :Launchy, "
|
16
|
+
autoload :Launchy, "calendar_assistant/extensions/launchy_extensions"
|
17
17
|
autoload :TOML, "toml"
|
18
18
|
autoload :Thor, "thor"
|
19
19
|
require "calendar_assistant/extensions/rainbow_extensions" # Rainbow() doesn't trigger autoload
|
@@ -25,17 +25,22 @@ require "active_support/time" # Time doesn't trigger autoload
|
|
25
25
|
require "calendar_assistant/calendar_assistant"
|
26
26
|
|
27
27
|
class CalendarAssistant
|
28
|
-
autoload :VERSION,
|
29
|
-
autoload :Config,
|
30
|
-
autoload :StringHelpers,
|
31
|
-
autoload :DateHelpers,
|
32
|
-
autoload :
|
33
|
-
autoload :
|
34
|
-
autoload :
|
35
|
-
autoload :
|
36
|
-
autoload :
|
37
|
-
autoload :
|
38
|
-
autoload :
|
28
|
+
autoload :VERSION, "calendar_assistant/version"
|
29
|
+
autoload :Config, "calendar_assistant/config"
|
30
|
+
autoload :StringHelpers, "calendar_assistant/string_helpers"
|
31
|
+
autoload :DateHelpers, "calendar_assistant/date_helpers"
|
32
|
+
autoload :HasDuration, "calendar_assistant/has_duration"
|
33
|
+
autoload :AvailableBlock, "calendar_assistant/available_block"
|
34
|
+
autoload :Event, "calendar_assistant/event"
|
35
|
+
autoload :EventRepository, "calendar_assistant/event_repository"
|
36
|
+
autoload :EventRepositoryFactory, "calendar_assistant/event_repository_factory"
|
37
|
+
autoload :EventSet, "calendar_assistant/event_set"
|
38
|
+
autoload :Scheduler, "calendar_assistant/scheduler"
|
39
|
+
autoload :LocalService, "calendar_assistant/local_service"
|
40
|
+
autoload :LocationEventRepository, "calendar_assistant/location_event_repository"
|
41
|
+
autoload :LintEventRepository, "calendar_assistant/lint_event_repository"
|
42
|
+
autoload :PredicateCollection, "calendar_assistant/predicate_collection"
|
43
|
+
autoload :LocationConfigValidator, "calendar_assistant/location_config_validator"
|
39
44
|
end
|
40
45
|
|
41
46
|
require "calendar_assistant/cli"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
class AvailableBlock
|
3
|
+
include HasDuration
|
4
|
+
|
5
|
+
attr_reader :start, :end
|
6
|
+
|
7
|
+
def initialize(**params)
|
8
|
+
@start = HasDuration.cast_datetime(params[:start]) if params[:start]
|
9
|
+
@end = HasDuration.cast_datetime(params[:end]) if params[:end]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -36,7 +36,7 @@ class CalendarAssistant
|
|
36
36
|
|
37
37
|
@calendar = service.get_calendar Config::DEFAULT_CALENDAR_ID
|
38
38
|
@event_repository_factory = event_repository_factory
|
39
|
-
@event_repositories = {} # calendar_id → event_repository
|
39
|
+
@event_repositories = {} # type, calendar_id → event_repository
|
40
40
|
@event_predicates = PredicateCollection.build(config.must_be, config.must_not_be)
|
41
41
|
end
|
42
42
|
|
@@ -56,16 +56,15 @@ class CalendarAssistant
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def lint_events time_range
|
59
|
-
calendar_ids = config.
|
59
|
+
calendar_ids = config.calendar_ids
|
60
60
|
if calendar_ids.length > 1
|
61
61
|
raise BaseException, "CalendarAssistant#lint_events only supports one person (for now)"
|
62
62
|
end
|
63
|
-
|
64
|
-
event_repository(calendar_ids.first).find(time_range, predicates: @event_predicates.merge({needs_action?: true}))
|
63
|
+
event_repository(calendar_ids.first, type: :lint).find(time_range, predicates: @event_predicates)
|
65
64
|
end
|
66
65
|
|
67
66
|
def find_events time_range
|
68
|
-
calendar_ids = config.
|
67
|
+
calendar_ids = config.calendar_ids
|
69
68
|
if calendar_ids.length > 1
|
70
69
|
raise BaseException, "CalendarAssistant#find_events only supports one person (for now)"
|
71
70
|
end
|
@@ -73,7 +72,7 @@ class CalendarAssistant
|
|
73
72
|
end
|
74
73
|
|
75
74
|
def availability time_range
|
76
|
-
calendar_ids = config.
|
75
|
+
calendar_ids = config.calendar_ids
|
77
76
|
ers = calendar_ids.map do |calendar_id|
|
78
77
|
event_repository calendar_id
|
79
78
|
end
|
@@ -81,47 +80,30 @@ class CalendarAssistant
|
|
81
80
|
end
|
82
81
|
|
83
82
|
def find_location_events time_range
|
84
|
-
|
85
|
-
event_set.new event_set.events.select { |e| e.location_event? }
|
83
|
+
event_repository(type: :location).find(time_range, predicates: @event_predicates)
|
86
84
|
end
|
87
85
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
deleted_events = []
|
96
|
-
modified_events = []
|
97
|
-
|
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
|
-
)
|
103
|
-
|
104
|
-
existing_event_set.events.each do |existing_event|
|
105
|
-
if existing_event.start_date >= event.start_date && existing_event.end_date <= event.end_date
|
106
|
-
event_repository.delete existing_event
|
107
|
-
deleted_events << existing_event
|
108
|
-
elsif existing_event.start_date <= event.end_date && existing_event.end_date > event.end_date
|
109
|
-
event_repository.update existing_event, start: range.last
|
110
|
-
modified_events << existing_event
|
111
|
-
elsif existing_event.start_date < event.start_date && existing_event.end_date >= event.start_date
|
112
|
-
event_repository.update existing_event, end: range.first
|
113
|
-
modified_events << existing_event
|
114
|
-
end
|
86
|
+
def create_location_events time_range, location
|
87
|
+
LocationConfigValidator.valid?(config)
|
88
|
+
|
89
|
+
event_set = EventSet::Hash.new(event_repository,{})
|
90
|
+
|
91
|
+
unique_calendar_ids.each do |calendar_id|
|
92
|
+
event_set[calendar_id] = event_repository(calendar_id, type: :location).create(time_range, location, predicates: @event_predicates)
|
115
93
|
end
|
116
94
|
|
117
|
-
|
118
|
-
|
119
|
-
response[:modified] = modified_events unless modified_events.empty?
|
95
|
+
event_set
|
96
|
+
end
|
120
97
|
|
121
|
-
|
98
|
+
def event_repository calendar_id=Config::DEFAULT_CALENDAR_ID, type: :base
|
99
|
+
@event_repositories[type] ||= {}
|
100
|
+
@event_repositories[type][calendar_id] ||=
|
101
|
+
@event_repository_factory.new_event_repository(@service, calendar_id, config: config, type: type)
|
122
102
|
end
|
123
103
|
|
124
|
-
|
125
|
-
|
104
|
+
private
|
105
|
+
|
106
|
+
def unique_calendar_ids
|
107
|
+
@unique_calendar_ids ||= Array(config.calendar_ids) | [Config::DEFAULT_CALENDAR_ID]
|
126
108
|
end
|
127
109
|
end
|
@@ -27,12 +27,12 @@ class CalendarAssistant
|
|
27
27
|
aliases: [ "-n" ]
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.
|
31
|
-
option CalendarAssistant::Config::Keys::Options::
|
30
|
+
def self.has_multiple_calendars
|
31
|
+
option CalendarAssistant::Config::Keys::Options::CALENDARS,
|
32
32
|
type: :string,
|
33
|
-
banner: "
|
33
|
+
banner: "CALENDAR1[,CALENDAR2[,...]]",
|
34
34
|
desc: "[default 'me'] people (email IDs) to whom this command will be applied",
|
35
|
-
aliases: ["-a"]
|
35
|
+
aliases: ["-a", "--attendees"]
|
36
36
|
end
|
37
37
|
|
38
38
|
default_config = CalendarAssistant::CLI::Config.new options: options # used in option descriptions
|
@@ -139,7 +139,7 @@ class CalendarAssistant
|
|
139
139
|
desc "lint [DATE | DATERANGE | TIMERANGE]",
|
140
140
|
"Lint your events for a date or range of dates (default 'today')"
|
141
141
|
will_create_a_service
|
142
|
-
|
142
|
+
has_multiple_calendars
|
143
143
|
|
144
144
|
has_events
|
145
145
|
def lint datespec = "today"
|
@@ -156,7 +156,7 @@ class CalendarAssistant
|
|
156
156
|
desc: "only show events that you've accepted with another person",
|
157
157
|
aliases: ["-c"]
|
158
158
|
will_create_a_service
|
159
|
-
|
159
|
+
has_multiple_calendars
|
160
160
|
|
161
161
|
has_events
|
162
162
|
def show datespec = "today"
|
@@ -207,17 +207,21 @@ class CalendarAssistant
|
|
207
207
|
|
208
208
|
desc "location-set LOCATION [DATE | DATERANGE]",
|
209
209
|
"Set your location to LOCATION for a date or range of dates (default 'today')"
|
210
|
+
option CalendarAssistant::Config::Keys::Options::FORCE,
|
211
|
+
type: :boolean,
|
212
|
+
desc: "will manage location across multiple calendars whether a nickname is set or not"
|
210
213
|
option CalendarAssistant::Config::Keys::Settings::VISIBILITY,
|
211
214
|
type: :string,
|
212
215
|
banner: "VISIBILITY",
|
213
|
-
desc: "[default is 'default'] Set the
|
216
|
+
desc: "[default is 'default'] Set the visibility of the event. Values are 'public', 'private', 'default'."
|
214
217
|
will_create_a_service
|
215
218
|
has_events
|
219
|
+
has_multiple_calendars
|
216
220
|
def location_set location = nil, datespec = "today"
|
217
221
|
return help! if location.nil?
|
218
222
|
|
219
223
|
calendar_assistant(datespec) do |ca, date, out|
|
220
|
-
event_set = ca.
|
224
|
+
event_set = ca.create_location_events date, location
|
221
225
|
out.print_events ca, event_set
|
222
226
|
end
|
223
227
|
end
|
@@ -243,7 +247,7 @@ class CalendarAssistant
|
|
243
247
|
desc: sprintf("[default %s] find chunks of available time before TIME (which is a BusinessTime string like '9am' or '14:30')",
|
244
248
|
default_config.setting(CalendarAssistant::Config::Keys::Settings::END_OF_DAY)),
|
245
249
|
aliases: ["-e"]
|
246
|
-
|
250
|
+
has_multiple_calendars
|
247
251
|
will_create_a_service
|
248
252
|
has_events
|
249
253
|
def availability datespec = "today"
|
@@ -1,6 +1,9 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
class CalendarAssistant
|
2
3
|
module CLI
|
3
4
|
class Printer
|
5
|
+
class LaunchUrlException < CalendarAssistant::BaseException ; end
|
6
|
+
|
4
7
|
|
5
8
|
attr_reader :io
|
6
9
|
|
@@ -9,7 +12,11 @@ class CalendarAssistant
|
|
9
12
|
end
|
10
13
|
|
11
14
|
def launch url
|
12
|
-
|
15
|
+
begin
|
16
|
+
Launchy.open(url)
|
17
|
+
rescue Exception => e
|
18
|
+
raise LaunchUrlException.new(e)
|
19
|
+
end
|
13
20
|
end
|
14
21
|
|
15
22
|
def puts *args
|
@@ -32,13 +39,13 @@ class CalendarAssistant
|
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
35
|
-
def print_events ca, event_set,
|
42
|
+
def print_events ca, event_set, presenter_class: CLI::EventSetPresenter
|
36
43
|
puts presenter_class.new(event_set, config: ca.config).to_s
|
37
44
|
puts
|
38
45
|
end
|
39
46
|
|
40
47
|
def print_available_blocks ca, event_set, omit_title: false
|
41
|
-
ers = ca.config.
|
48
|
+
ers = ca.config.calendar_ids.map {|calendar_id| ca.event_repository calendar_id}
|
42
49
|
time_zones = ers.map {|er| er.calendar.time_zone}.uniq
|
43
50
|
|
44
51
|
unless omit_title
|
@@ -33,13 +33,14 @@ class CalendarAssistant
|
|
33
33
|
module Options
|
34
34
|
COMMITMENTS = "commitments" # bool
|
35
35
|
JOIN = "join" # bool
|
36
|
-
|
36
|
+
CALENDARS = "calendars" # array of calendar ids (comma-delimited)
|
37
37
|
LOCAL_STORE = "local-store" # filename
|
38
38
|
DEBUG = "debug" # bool
|
39
39
|
FORMATTING = "formatting" # Rainbow
|
40
40
|
MUST_BE = "must-be" # array of event predicates (comma-delimited)
|
41
41
|
MUST_NOT_BE = "must-not-be" # array of event predicates (comma-delimited)
|
42
42
|
CONTEXT = "context" # symbol referring to command context
|
43
|
+
FORCE = "force" # bool
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
@@ -50,7 +51,7 @@ class CalendarAssistant
|
|
50
51
|
Keys::Settings::MEETING_LENGTH => "30m", # ChronicDuration
|
51
52
|
Keys::Settings::START_OF_DAY => "9am", # BusinessTime
|
52
53
|
Keys::Settings::END_OF_DAY => "6pm", # BusinessTime
|
53
|
-
Keys::Options::
|
54
|
+
Keys::Options::CALENDARS => [DEFAULT_CALENDAR_ID], # array of calendar ids
|
54
55
|
Keys::Options::FORMATTING => true, # Rainbow
|
55
56
|
}
|
56
57
|
|
@@ -145,10 +146,10 @@ class CalendarAssistant
|
|
145
146
|
end
|
146
147
|
|
147
148
|
#
|
148
|
-
# helper method for Keys::Options::
|
149
|
+
# helper method for Keys::Options::CALENDARS
|
149
150
|
#
|
150
|
-
def
|
151
|
-
split_if_array(Keys::Options::
|
151
|
+
def calendar_ids
|
152
|
+
split_if_array(Keys::Options::CALENDARS)
|
152
153
|
end
|
153
154
|
|
154
155
|
def must_be
|
@@ -190,32 +191,24 @@ class CalendarAssistant
|
|
190
191
|
end
|
191
192
|
|
192
193
|
def self.find_in_hash hash, keypath
|
193
|
-
current_val
|
194
|
-
|
195
|
-
|
196
|
-
keypath.each do |key|
|
197
|
-
if current_val.has_key?(key)
|
198
|
-
current_val = current_val[key]
|
199
|
-
else
|
200
|
-
current_val = nil
|
201
|
-
break
|
202
|
-
end
|
194
|
+
split_keypath(keypath).inject(hash) do |current_val, key|
|
195
|
+
break unless current_val.has_key?(key)
|
196
|
+
current_val[key]
|
203
197
|
end
|
204
|
-
|
205
|
-
current_val
|
206
198
|
end
|
207
199
|
|
208
200
|
def self.set_in_hash hash, keypath, new_value
|
209
|
-
|
210
|
-
keypath = keypath.split(".") unless keypath.is_a?(Array)
|
211
|
-
*path_parts, key = *keypath
|
201
|
+
*path_parts, key = *split_keypath(keypath)
|
212
202
|
|
213
|
-
path_parts.
|
214
|
-
|
215
|
-
current_hash = current_hash[path_part]
|
203
|
+
current_hash = path_parts.inject(hash) do |current_val, path|
|
204
|
+
current_val[path] ||= {}
|
216
205
|
end
|
217
206
|
|
218
207
|
current_hash[key] = new_value
|
219
208
|
end
|
209
|
+
|
210
|
+
def self.split_keypath(keypath)
|
211
|
+
keypath.is_a?(Array) ? keypath : keypath.split(".")
|
212
|
+
end
|
220
213
|
end
|
221
214
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class Event < SimpleDelegator
|
3
|
-
|
3
|
+
include HasDuration
|
4
|
+
|
4
5
|
# constants describing enumerated attribute values
|
5
6
|
# see https://developers.google.com/calendar/v3/reference/events
|
6
7
|
#
|
@@ -69,9 +70,6 @@ class CalendarAssistant
|
|
69
70
|
#
|
70
71
|
# class methods
|
71
72
|
#
|
72
|
-
def self.duration_in_seconds start_time, end_time
|
73
|
-
(end_time.to_datetime - start_time.to_datetime).days.to_i
|
74
|
-
end
|
75
73
|
|
76
74
|
def self.location_event_prefix config
|
77
75
|
icon = config[CalendarAssistant::Config::Keys::Settings::LOCATION_ICON]
|
@@ -98,30 +96,6 @@ class CalendarAssistant
|
|
98
96
|
!! summary.try(:starts_with?, Event.location_event_prefix(@config))
|
99
97
|
end
|
100
98
|
|
101
|
-
def all_day?
|
102
|
-
start.try(:date) || self.end.try(:date)
|
103
|
-
end
|
104
|
-
|
105
|
-
def past?
|
106
|
-
if all_day?
|
107
|
-
Date.today >= end_date
|
108
|
-
else
|
109
|
-
Time.now >= end_time
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def current?
|
114
|
-
! (past? || future?)
|
115
|
-
end
|
116
|
-
|
117
|
-
def future?
|
118
|
-
if all_day?
|
119
|
-
start_date > Date.today
|
120
|
-
else
|
121
|
-
start_time > Time.now
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
99
|
def accepted?
|
126
100
|
response_status == CalendarAssistant::Event::Response::ACCEPTED
|
127
101
|
end
|
@@ -191,56 +165,6 @@ class CalendarAssistant
|
|
191
165
|
gcsog.nil? ? true : !!gcsog
|
192
166
|
end
|
193
167
|
|
194
|
-
def start_time
|
195
|
-
if all_day?
|
196
|
-
start_date.beginning_of_day
|
197
|
-
else
|
198
|
-
start.date_time
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def start_date
|
203
|
-
if all_day?
|
204
|
-
start.to_date
|
205
|
-
else
|
206
|
-
start_time.to_date
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def end_time
|
211
|
-
if all_day?
|
212
|
-
end_date.beginning_of_day
|
213
|
-
else
|
214
|
-
self.end.date_time
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def end_date
|
219
|
-
if all_day?
|
220
|
-
self.end.to_date
|
221
|
-
else
|
222
|
-
end_time.to_date
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def duration
|
227
|
-
if all_day?
|
228
|
-
days = (end_date - start_date).to_i
|
229
|
-
return "#{days}d"
|
230
|
-
end
|
231
|
-
|
232
|
-
p = ActiveSupport::Duration.build(duration_in_seconds).parts
|
233
|
-
s = []
|
234
|
-
s << "#{p[:hours]}h" if p.has_key?(:hours)
|
235
|
-
s << "#{p[:minutes]}m" if p.has_key?(:minutes)
|
236
|
-
s.join(" ")
|
237
|
-
end
|
238
|
-
|
239
|
-
|
240
|
-
def duration_in_seconds
|
241
|
-
Event.duration_in_seconds start_time, end_time
|
242
|
-
end
|
243
|
-
|
244
168
|
def other_human_attendees
|
245
169
|
return nil if attendees.nil?
|
246
170
|
attendees.select { |a| ! a.resource && ! a.self }
|
@@ -278,10 +202,5 @@ class CalendarAssistant
|
|
278
202
|
nil
|
279
203
|
end
|
280
204
|
end
|
281
|
-
|
282
|
-
def contains? time
|
283
|
-
start_time <= time && time < end_time
|
284
|
-
end
|
285
|
-
|
286
205
|
end
|
287
|
-
end
|
206
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class EventRepository
|
3
|
+
class CalendarNotFoundException < CalendarAssistant::BaseException;
|
4
|
+
end
|
5
|
+
|
3
6
|
attr_reader :calendar, :calendar_id, :config
|
4
7
|
|
5
8
|
def initialize(service, calendar_id, config: CalendarAssistant::Config.new)
|
@@ -8,7 +11,7 @@ class CalendarAssistant
|
|
8
11
|
@calendar_id = calendar_id
|
9
12
|
@calendar = @service.get_calendar @calendar_id
|
10
13
|
rescue Google::Apis::ClientError => e
|
11
|
-
raise
|
14
|
+
raise CalendarNotFoundException, "Calendar for #{@calendar_id} not found" if e.status_code == 404
|
12
15
|
raise
|
13
16
|
end
|
14
17
|
|
@@ -46,6 +49,7 @@ class CalendarAssistant
|
|
46
49
|
|
47
50
|
def delete event
|
48
51
|
@service.delete_event @calendar_id, event.id
|
52
|
+
event
|
49
53
|
end
|
50
54
|
|
51
55
|
def update(event, attributes)
|
@@ -54,15 +58,6 @@ class CalendarAssistant
|
|
54
58
|
CalendarAssistant::Event.new(updated_event, config: config)
|
55
59
|
end
|
56
60
|
|
57
|
-
def available_block start_time, end_time
|
58
|
-
e = Google::Apis::CalendarV3::Event.new(
|
59
|
-
start: Google::Apis::CalendarV3::EventDateTime.new(date_time: start_time.in_time_zone(calendar.time_zone).to_datetime),
|
60
|
-
end: Google::Apis::CalendarV3::EventDateTime.new(date_time: end_time.in_time_zone(calendar.time_zone).to_datetime),
|
61
|
-
summary: "available"
|
62
|
-
)
|
63
|
-
CalendarAssistant::Event.new e, config: config
|
64
|
-
end
|
65
|
-
|
66
61
|
private
|
67
62
|
|
68
63
|
def filter_by_predicates(events, predicates)
|
@@ -1,7 +1,16 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class EventRepositoryFactory
|
3
|
-
def self.new_event_repository service, calendar_id, config: CalendarAssistant::Config.new
|
4
|
-
|
3
|
+
def self.new_event_repository service, calendar_id, config: CalendarAssistant::Config.new, type: :base
|
4
|
+
klass = case type
|
5
|
+
when :location
|
6
|
+
LocationEventRepository
|
7
|
+
when :lint
|
8
|
+
LintEventRepository
|
9
|
+
else
|
10
|
+
EventRepository
|
11
|
+
end
|
12
|
+
|
13
|
+
klass.new service, calendar_id, config: config
|
5
14
|
end
|
6
15
|
end
|
7
16
|
end
|
@@ -8,6 +8,9 @@ class CalendarAssistant
|
|
8
8
|
#
|
9
9
|
class EventSet
|
10
10
|
def self.new event_repository, events=nil
|
11
|
+
if events.is_a?(EventSet::Hash)
|
12
|
+
return EventSet::Hash.new event_repository, events.try(:events)
|
13
|
+
end
|
11
14
|
if events.is_a?(::Hash)
|
12
15
|
return EventSet::Hash.new event_repository, events
|
13
16
|
end
|
@@ -55,6 +58,14 @@ class CalendarAssistant
|
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
61
|
+
def [] key
|
62
|
+
events[key] ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
def []= key, value
|
66
|
+
events[key] = value
|
67
|
+
end
|
68
|
+
|
58
69
|
def available_blocks length: 1
|
59
70
|
event_repository.in_tz do
|
60
71
|
dates = events.keys.sort
|
@@ -76,15 +87,15 @@ class CalendarAssistant
|
|
76
87
|
next if Time.before_business_hours?(e.end_time.to_time)
|
77
88
|
next if Time.after_business_hours?(e.start_time.to_time)
|
78
89
|
|
79
|
-
if
|
80
|
-
avail_time[date] <<
|
90
|
+
if HasDuration.duration_in_seconds(start_time, e.start_time) >= length
|
91
|
+
avail_time[date] << AvailableBlock.new(start: start_time, end: e.start_time)
|
81
92
|
end
|
82
93
|
start_time = [e.end_time, start_time].max
|
83
94
|
break if ! start_time.during_business_hours?
|
84
95
|
end
|
85
96
|
|
86
|
-
if
|
87
|
-
avail_time[date] <<
|
97
|
+
if HasDuration.duration_in_seconds(start_time, end_time) >= length
|
98
|
+
avail_time[date] << AvailableBlock.new(start: start_time, end: end_time)
|
88
99
|
end
|
89
100
|
|
90
101
|
avail_time
|
@@ -106,8 +117,8 @@ class CalendarAssistant
|
|
106
117
|
event_b.contains?(event_a.end_time-1)
|
107
118
|
start_time = [event_a.start_time, event_b.start_time].max
|
108
119
|
end_time = [event_a.end_time, event_b.end_time ].min
|
109
|
-
if
|
110
|
-
set.events[date] <<
|
120
|
+
if HasDuration.duration_in_seconds(start_time, end_time) >= length
|
121
|
+
set.events[date] << AvailableBlock.new(start: start_time, end: end_time)
|
111
122
|
end
|
112
123
|
end
|
113
124
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "launchy"
|
2
|
+
|
3
|
+
#
|
4
|
+
# extend Launchy to handle zoom web URLs via the zoom commandline
|
5
|
+
# executable.
|
6
|
+
#
|
7
|
+
# note this doesn't handle "personal links" like
|
8
|
+
#
|
9
|
+
# "https://robin.zoom.us/my/usernamehere"
|
10
|
+
#
|
11
|
+
# which depends on an http 302 redirect from the zoom site
|
12
|
+
#
|
13
|
+
class CalendarAssistant
|
14
|
+
class ZoomLaunchy < Launchy::Application::Browser
|
15
|
+
ZOOM_URI_REGEXP = %r(https?://\w+.zoom.us/j/(\d+))
|
16
|
+
|
17
|
+
def self.handles? uri
|
18
|
+
return true if ZOOM_URI_REGEXP.match(uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
def darwin_app_list
|
22
|
+
[find_executable("open")]
|
23
|
+
end
|
24
|
+
|
25
|
+
def nix_app_list
|
26
|
+
[find_executable("xdg-open")]
|
27
|
+
end
|
28
|
+
|
29
|
+
def open uri, options={}
|
30
|
+
command = host_os_family.app_list(self).compact.first
|
31
|
+
if command.nil?
|
32
|
+
super uri, options
|
33
|
+
else
|
34
|
+
confno = ZOOM_URI_REGEXP.match(uri)[1]
|
35
|
+
url = "zoommtg://zoom.us/join?confno=#{confno}"
|
36
|
+
run command, [url]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# we need to be first so we get right of first refusal on `https?` URLs
|
43
|
+
Launchy::Application.children.delete(CalendarAssistant::ZoomLaunchy)
|
44
|
+
Launchy::Application.children.prepend(CalendarAssistant::ZoomLaunchy)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
module HasDuration
|
3
|
+
def self.duration_in_seconds start_time, end_time
|
4
|
+
(end_time.to_datetime - start_time.to_datetime).days.to_i
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.cast_datetime(datetime, time_zone = Time.zone.name)
|
9
|
+
return datetime if datetime.is_a?(Google::Apis::CalendarV3::EventDateTime)
|
10
|
+
Google::Apis::CalendarV3::EventDateTime.new(date_time: datetime.in_time_zone(time_zone).to_datetime)
|
11
|
+
end
|
12
|
+
|
13
|
+
def all_day?
|
14
|
+
start.try(:date) || self.end.try(:date)
|
15
|
+
end
|
16
|
+
|
17
|
+
def past?
|
18
|
+
if all_day?
|
19
|
+
Date.today >= end_date
|
20
|
+
else
|
21
|
+
Time.now >= end_time
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def current?
|
26
|
+
!(past? || future?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def future?
|
30
|
+
if all_day?
|
31
|
+
start_date > Date.today
|
32
|
+
else
|
33
|
+
start_time > Time.now
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def cover?(event)
|
38
|
+
event.start_date >= start_date && event.end_date <= end_date
|
39
|
+
end
|
40
|
+
|
41
|
+
def overlaps_start_of?(event)
|
42
|
+
event.start_date <= end_date && event.end_date > end_date
|
43
|
+
end
|
44
|
+
|
45
|
+
def overlaps_end_of?(event)
|
46
|
+
event.start_date < start_date && event.end_date >= start_date
|
47
|
+
end
|
48
|
+
|
49
|
+
def start_time
|
50
|
+
if all_day?
|
51
|
+
start_date.beginning_of_day
|
52
|
+
else
|
53
|
+
start.date_time
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def start_date
|
58
|
+
if all_day?
|
59
|
+
start.to_date
|
60
|
+
else
|
61
|
+
start_time.to_date
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def end_time
|
66
|
+
if all_day?
|
67
|
+
end_date.beginning_of_day
|
68
|
+
else
|
69
|
+
self.end.date_time
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def end_date
|
74
|
+
if all_day?
|
75
|
+
self.end.to_date
|
76
|
+
else
|
77
|
+
end_time.to_date
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def duration
|
82
|
+
if all_day?
|
83
|
+
days = (end_date - start_date).to_i
|
84
|
+
return "#{days}d"
|
85
|
+
end
|
86
|
+
|
87
|
+
p = ActiveSupport::Duration.build(duration_in_seconds).parts
|
88
|
+
s = []
|
89
|
+
s << "#{p[:hours]}h" if p.has_key?(:hours)
|
90
|
+
s << "#{p[:minutes]}m" if p.has_key?(:minutes)
|
91
|
+
s.join(" ")
|
92
|
+
end
|
93
|
+
|
94
|
+
def duration_in_seconds
|
95
|
+
HasDuration.duration_in_seconds start_time, end_time
|
96
|
+
end
|
97
|
+
|
98
|
+
def contains? time
|
99
|
+
start_time <= time && time < end_time
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
class CalendarAssistant
|
3
|
+
class LocationConfigValidator
|
4
|
+
class LocationConfigValidationException < CalendarAssistant::BaseException;
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.valid?(config)
|
8
|
+
return if (config.calendar_ids - [ Config::DEFAULT_CALENDAR_ID ]).empty?
|
9
|
+
return if !!config[CalendarAssistant::Config::Keys::Settings::NICKNAME]
|
10
|
+
return if !!config[CalendarAssistant::Config::Keys::Options::FORCE]
|
11
|
+
|
12
|
+
raise LocationConfigValidationException, "Managing location across multiple calendars when a nickname is not set is not recommended, use --force to override"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
class LocationEventRepository < EventRepository
|
3
|
+
def find time, predicates: {}
|
4
|
+
event_set = super time, predicates: predicates
|
5
|
+
event_set.new event_set.events.select { |e| e.location_event? }
|
6
|
+
end
|
7
|
+
|
8
|
+
def create time, location, predicates: {}
|
9
|
+
# find pre-existing events that overlap
|
10
|
+
existing_event_set = find time, predicates: predicates
|
11
|
+
|
12
|
+
# augment event end date appropriately
|
13
|
+
range = CalendarAssistant.date_range_cast time
|
14
|
+
|
15
|
+
|
16
|
+
event = super(
|
17
|
+
transparency: CalendarAssistant::Event::Transparency::TRANSPARENT,
|
18
|
+
start: range.first, end: range.last,
|
19
|
+
summary: "#{Event.location_event_prefix(@config)}#{location}"
|
20
|
+
)
|
21
|
+
|
22
|
+
modify_location_events(event, existing_event_set)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def modify_location_events(event, existing_event_set)
|
28
|
+
response = existing_event_set.new({created: [event]})
|
29
|
+
|
30
|
+
existing_event_set.events.each do |existing_event|
|
31
|
+
if event.cover?(existing_event)
|
32
|
+
response[:deleted] << delete(existing_event)
|
33
|
+
elsif event.overlaps_start_of?(existing_event)
|
34
|
+
response[:modified] << update(existing_event, start: event.end_date)
|
35
|
+
elsif event.overlaps_end_of?(existing_event)
|
36
|
+
response[:modified] << update(existing_event, end: event.start_date)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
response
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
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.9.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-
|
12
|
+
date: 2019-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -149,14 +149,14 @@ dependencies:
|
|
149
149
|
requirements:
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
152
|
+
version: 0.14.8
|
153
153
|
type: :development
|
154
154
|
prerelease: false
|
155
155
|
version_requirements: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version:
|
159
|
+
version: 0.14.8
|
160
160
|
- !ruby/object:Gem::Dependency
|
161
161
|
name: bundler
|
162
162
|
requirement: !ruby/object:Gem::Requirement
|
@@ -285,6 +285,7 @@ files:
|
|
285
285
|
- Rakefile
|
286
286
|
- bin/calendar-assistant
|
287
287
|
- lib/calendar_assistant.rb
|
288
|
+
- lib/calendar_assistant/available_block.rb
|
288
289
|
- lib/calendar_assistant/calendar_assistant.rb
|
289
290
|
- lib/calendar_assistant/cli.rb
|
290
291
|
- lib/calendar_assistant/cli/authorizer.rb
|
@@ -305,8 +306,13 @@ files:
|
|
305
306
|
- lib/calendar_assistant/event_repository_factory.rb
|
306
307
|
- lib/calendar_assistant/event_set.rb
|
307
308
|
- lib/calendar_assistant/extensions/google_apis_extensions.rb
|
309
|
+
- lib/calendar_assistant/extensions/launchy_extensions.rb
|
308
310
|
- lib/calendar_assistant/extensions/rainbow_extensions.rb
|
311
|
+
- lib/calendar_assistant/has_duration.rb
|
312
|
+
- lib/calendar_assistant/lint_event_repository.rb
|
309
313
|
- lib/calendar_assistant/local_service.rb
|
314
|
+
- lib/calendar_assistant/location_config_validator.rb
|
315
|
+
- lib/calendar_assistant/location_event_repository.rb
|
310
316
|
- lib/calendar_assistant/predicate_collection.rb
|
311
317
|
- lib/calendar_assistant/scheduler.rb
|
312
318
|
- lib/calendar_assistant/string_helpers.rb
|