calendar-assistant 0.8.0 → 0.9.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 +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
|
+
[![Gem Version](https://badge.fury.io/rb/calendar-assistant.svg)](https://badge.fury.io/rb/calendar-assistant)
|
12
13
|
[![Concourse CI](https://ci.nokogiri.org/api/v1/teams/calendar-assistants/pipelines/calendar-assistant/jobs/rake-spec/badge)](https://ci.nokogiri.org/teams/calendar-assistants/pipelines/calendar-assistant)
|
13
14
|
[![Maintainability](https://api.codeclimate.com/v1/badges/3525792e1feeccfd8875/maintainability)](https://codeclimate.com/github/flavorjones/calendar-assistant/maintainability)
|
14
15
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/3525792e1feeccfd8875/test_coverage)](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
|