calendar-assistant 0.9.0 → 0.14.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/Gemfile +2 -1
- data/README.md +44 -22
- data/Rakefile +37 -6
- data/bin/calendar-assistant +3 -1
- data/lib/calendar_assistant.rb +14 -16
- data/lib/calendar_assistant/available_block.rb +1 -1
- data/lib/calendar_assistant/calendar_assistant.rb +21 -25
- data/lib/calendar_assistant/cli.rb +10 -10
- data/lib/calendar_assistant/cli/authorizer.rb +14 -14
- data/lib/calendar_assistant/cli/command_service.rb +2 -2
- data/lib/calendar_assistant/cli/commands.rb +47 -40
- data/lib/calendar_assistant/cli/config.rb +16 -17
- data/lib/calendar_assistant/cli/event_presenter.rb +2 -2
- data/lib/calendar_assistant/cli/event_set_presenter.rb +3 -3
- data/lib/calendar_assistant/cli/helpers.rb +7 -8
- data/lib/calendar_assistant/cli/linter_event_presenter.rb +3 -3
- data/lib/calendar_assistant/cli/linter_event_set_presenter.rb +4 -4
- data/lib/calendar_assistant/cli/printer.rb +15 -15
- data/lib/calendar_assistant/config.rb +11 -11
- data/lib/calendar_assistant/config/token_store.rb +4 -4
- data/lib/calendar_assistant/date_helpers.rb +1 -2
- data/lib/calendar_assistant/event.rb +54 -50
- data/lib/calendar_assistant/event_repository.rb +14 -15
- data/lib/calendar_assistant/event_repository_factory.rb +1 -1
- data/lib/calendar_assistant/event_set.rb +14 -14
- data/lib/calendar_assistant/extensions/google_apis_extensions.rb +1 -1
- data/lib/calendar_assistant/extensions/launchy_extensions.rb +7 -4
- data/lib/calendar_assistant/has_duration.rb +4 -5
- data/lib/calendar_assistant/lint_event_repository.rb +3 -3
- data/lib/calendar_assistant/location_config_validator.rb +3 -3
- data/lib/calendar_assistant/location_event_repository.rb +7 -8
- data/lib/calendar_assistant/predicate_collection.rb +1 -2
- data/lib/calendar_assistant/scheduler.rb +4 -5
- data/lib/calendar_assistant/string_helpers.rb +1 -1
- data/lib/calendar_assistant/version.rb +1 -1
- metadata +38 -30
@@ -3,9 +3,10 @@ class CalendarAssistant
|
|
3
3
|
class Config
|
4
4
|
autoload :TokenStore, "calendar_assistant/config/token_store"
|
5
5
|
|
6
|
-
class NoTokensAuthorized < CalendarAssistant::BaseException
|
6
|
+
class NoTokensAuthorized < CalendarAssistant::BaseException
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
|
+
class AccessingHashAsScalar < CalendarAssistant::BaseException
|
9
10
|
end
|
10
11
|
|
11
12
|
module Keys
|
@@ -57,16 +58,15 @@ class CalendarAssistant
|
|
57
58
|
|
58
59
|
attr_reader :user_config, :options, :defaults
|
59
60
|
|
60
|
-
def initialize
|
61
|
+
def initialize(options: {},
|
61
62
|
user_config: {},
|
62
|
-
defaults: DEFAULT_SETTINGS
|
63
|
-
|
63
|
+
defaults: DEFAULT_SETTINGS)
|
64
64
|
@defaults = defaults
|
65
65
|
@options = options
|
66
66
|
@user_config = user_config
|
67
67
|
end
|
68
68
|
|
69
|
-
def in_env
|
69
|
+
def in_env(&block)
|
70
70
|
# this is totally not thread-safe
|
71
71
|
orig_b_o_d = BusinessTime::Config.beginning_of_workday
|
72
72
|
orig_e_o_d = BusinessTime::Config.end_of_workday
|
@@ -98,7 +98,7 @@ class CalendarAssistant
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
-
def get
|
101
|
+
def get(keypath)
|
102
102
|
rval = Config.find_in_hash(user_config, keypath)
|
103
103
|
|
104
104
|
if rval.is_a?(Hash)
|
@@ -108,7 +108,7 @@ class CalendarAssistant
|
|
108
108
|
rval
|
109
109
|
end
|
110
110
|
|
111
|
-
def set
|
111
|
+
def set(keypath, value)
|
112
112
|
Config.set_in_hash user_config, keypath, value
|
113
113
|
end
|
114
114
|
|
@@ -116,7 +116,7 @@ class CalendarAssistant
|
|
116
116
|
# note that, despite the name, this method returns both options
|
117
117
|
# and settings
|
118
118
|
#
|
119
|
-
def setting
|
119
|
+
def setting(setting_name)
|
120
120
|
context = Config.find_in_hash(options, Keys::Options::CONTEXT)
|
121
121
|
Config.find_in_hash(options, setting_name) ||
|
122
122
|
Config.find_in_hash(user_config, [Keys::SETTINGS, context, setting_name]) ||
|
@@ -190,14 +190,14 @@ class CalendarAssistant
|
|
190
190
|
a
|
191
191
|
end
|
192
192
|
|
193
|
-
def self.find_in_hash
|
193
|
+
def self.find_in_hash(hash, keypath)
|
194
194
|
split_keypath(keypath).inject(hash) do |current_val, key|
|
195
195
|
break unless current_val.has_key?(key)
|
196
196
|
current_val[key]
|
197
197
|
end
|
198
198
|
end
|
199
199
|
|
200
|
-
def self.set_in_hash
|
200
|
+
def self.set_in_hash(hash, keypath, new_value)
|
201
201
|
*path_parts, key = *split_keypath(keypath)
|
202
202
|
|
203
203
|
current_hash = path_parts.inject(hash) do |current_val, path|
|
@@ -3,20 +3,20 @@ class CalendarAssistant
|
|
3
3
|
class TokenStore
|
4
4
|
attr_reader :config
|
5
5
|
|
6
|
-
def initialize
|
6
|
+
def initialize(config)
|
7
7
|
@config = config
|
8
8
|
end
|
9
9
|
|
10
|
-
def delete
|
10
|
+
def delete(id)
|
11
11
|
config.tokens.delete(id)
|
12
12
|
config.persist!
|
13
13
|
end
|
14
14
|
|
15
|
-
def load
|
15
|
+
def load(id)
|
16
16
|
config.tokens[id]
|
17
17
|
end
|
18
18
|
|
19
|
-
def store
|
19
|
+
def store(id, token)
|
20
20
|
config.tokens[id] = token
|
21
21
|
config.persist!
|
22
22
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
module DateHelpers
|
3
|
-
def self.cast_dates
|
3
|
+
def self.cast_dates(attributes)
|
4
4
|
attributes.each_with_object({}) do |(key, value), object|
|
5
5
|
if value.is_a?(Time) || value.is_a?(DateTime)
|
6
6
|
object[key] = Google::Apis::CalendarV3::EventDateTime.new(date_time: value)
|
@@ -13,4 +13,3 @@ class CalendarAssistant
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
@@ -29,49 +29,49 @@ class CalendarAssistant
|
|
29
29
|
end
|
30
30
|
|
31
31
|
PREDICATES = {
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
]
|
32
|
+
"response": %I[
|
33
|
+
accepted?
|
34
|
+
declined?
|
35
|
+
awaiting?
|
36
|
+
tentative?
|
37
|
+
],
|
38
|
+
"temporal": %I[
|
39
|
+
all_day?
|
40
|
+
past?
|
41
|
+
current?
|
42
|
+
future?
|
43
|
+
],
|
44
|
+
"visibility": %I[
|
45
|
+
private?
|
46
|
+
public?
|
47
|
+
explicitly_visible?
|
48
|
+
visible_guestlist?
|
49
|
+
],
|
50
|
+
"attributes": %I[
|
51
|
+
location_event?
|
52
|
+
self?
|
53
|
+
one_on_one?
|
54
|
+
busy?
|
55
|
+
commitment?
|
56
|
+
recurring?
|
57
|
+
abandoned?
|
58
|
+
anyone_can_add_self?
|
59
|
+
attendees_omitted?
|
60
|
+
end_time_unspecified?
|
61
|
+
guests_can_invite_others?
|
62
|
+
guests_can_modify?
|
63
|
+
guests_can_see_other_guests?
|
64
|
+
private_copy?
|
65
|
+
locked?
|
66
|
+
needs_action?
|
67
|
+
],
|
68
68
|
}
|
69
69
|
|
70
70
|
#
|
71
71
|
# class methods
|
72
72
|
#
|
73
73
|
|
74
|
-
def self.location_event_prefix
|
74
|
+
def self.location_event_prefix(config)
|
75
75
|
icon = config[CalendarAssistant::Config::Keys::Settings::LOCATION_ICON]
|
76
76
|
if nickname = config[CalendarAssistant::Config::Keys::Settings::NICKNAME]
|
77
77
|
return "#{icon} #{nickname} @ "
|
@@ -87,13 +87,13 @@ class CalendarAssistant
|
|
87
87
|
@config = config
|
88
88
|
end
|
89
89
|
|
90
|
-
def update
|
90
|
+
def update(**args)
|
91
91
|
update!(**args)
|
92
92
|
self
|
93
93
|
end
|
94
94
|
|
95
95
|
def location_event?
|
96
|
-
!!
|
96
|
+
!!summary.try(:starts_with?, Event.location_event_prefix(@config))
|
97
97
|
end
|
98
98
|
|
99
99
|
def accepted?
|
@@ -167,15 +167,15 @@ class CalendarAssistant
|
|
167
167
|
|
168
168
|
def other_human_attendees
|
169
169
|
return nil if attendees.nil?
|
170
|
-
attendees.select { |a| !
|
170
|
+
attendees.select { |a| !a.resource && !a.self }
|
171
171
|
end
|
172
172
|
|
173
173
|
def human_attendees
|
174
174
|
return nil if attendees.nil?
|
175
|
-
attendees.select { |a| !
|
175
|
+
attendees.select { |a| !a.resource }
|
176
176
|
end
|
177
177
|
|
178
|
-
def attendee
|
178
|
+
def attendee(id)
|
179
179
|
return nil if attendees.nil?
|
180
180
|
attendees.find do |attendee|
|
181
181
|
attendee.email == id
|
@@ -192,15 +192,19 @@ class CalendarAssistant
|
|
192
192
|
|
193
193
|
def av_uri
|
194
194
|
@av_uri ||= begin
|
195
|
-
|
196
|
-
|
195
|
+
if conference_data && conference_data.conference_solution.name == "Zoom Meeting"
|
196
|
+
return conference_data.entry_points.detect{|d| d.entry_point_type == "video" }.uri
|
197
|
+
end
|
197
198
|
|
198
|
-
|
199
|
-
|
199
|
+
description_link = CalendarAssistant::StringHelpers.find_uri_for_domain(description, "zoom.us")
|
200
|
+
return description_link if description_link
|
200
201
|
|
201
|
-
|
202
|
-
|
203
|
-
|
202
|
+
location_link = CalendarAssistant::StringHelpers.find_uri_for_domain(location, "zoom.us")
|
203
|
+
return location_link if location_link
|
204
|
+
|
205
|
+
return hangout_link if hangout_link
|
206
|
+
nil
|
207
|
+
end
|
204
208
|
end
|
205
209
|
end
|
206
|
-
end
|
210
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class EventRepository
|
3
|
-
class CalendarNotFoundException < CalendarAssistant::BaseException
|
3
|
+
class CalendarNotFoundException < CalendarAssistant::BaseException
|
4
4
|
end
|
5
5
|
|
6
6
|
attr_reader :calendar, :calendar_id, :config
|
@@ -15,45 +15,44 @@ class CalendarAssistant
|
|
15
15
|
raise
|
16
16
|
end
|
17
17
|
|
18
|
-
def in_tz
|
18
|
+
def in_tz(&block)
|
19
19
|
CalendarAssistant.in_tz calendar.time_zone do
|
20
20
|
yield
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
def find
|
24
|
+
def find(time_range, predicates: {})
|
25
25
|
events = @service.list_events(@calendar_id,
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
)
|
26
|
+
time_min: time_range.first.iso8601,
|
27
|
+
time_max: time_range.last.iso8601,
|
28
|
+
order_by: "startTime",
|
29
|
+
single_events: true,
|
30
|
+
max_results: 2000)
|
32
31
|
events = events.items.map { |e| CalendarAssistant::Event.new(e, config: config) }
|
33
32
|
|
34
33
|
events = filter_by_predicates(events, predicates) unless predicates.empty?
|
35
34
|
CalendarAssistant::EventSet.new self, events
|
36
35
|
end
|
37
36
|
|
38
|
-
def new
|
39
|
-
event = Google::Apis::CalendarV3::Event.new
|
37
|
+
def new(event_attributes)
|
38
|
+
event = Google::Apis::CalendarV3::Event.new(**DateHelpers.cast_dates(event_attributes))
|
40
39
|
event.visibility ||= config.event_visibility
|
41
40
|
CalendarAssistant::Event.new(event, config: config)
|
42
41
|
end
|
43
42
|
|
44
|
-
def create
|
43
|
+
def create(event_attributes)
|
45
44
|
new(event_attributes).tap do |event|
|
46
45
|
@service.insert_event @calendar_id, event.__getobj__
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
50
|
-
def delete
|
51
|
-
@service.delete_event @calendar_id,
|
49
|
+
def delete(event)
|
50
|
+
@service.delete_event @calendar_id, event.id
|
52
51
|
event
|
53
52
|
end
|
54
53
|
|
55
54
|
def update(event, attributes)
|
56
|
-
event.update!
|
55
|
+
event.update!(**DateHelpers.cast_dates(attributes))
|
57
56
|
updated_event = @service.update_event @calendar_id, event.id, event
|
58
57
|
CalendarAssistant::Event.new(updated_event, config: config)
|
59
58
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class EventRepositoryFactory
|
3
|
-
def self.new_event_repository
|
3
|
+
def self.new_event_repository(service, calendar_id, config: CalendarAssistant::Config.new, type: :base)
|
4
4
|
klass = case type
|
5
5
|
when :location
|
6
6
|
LocationEventRepository
|
@@ -7,7 +7,7 @@ class CalendarAssistant
|
|
7
7
|
# - it could be a bare Event
|
8
8
|
#
|
9
9
|
class EventSet
|
10
|
-
def self.new
|
10
|
+
def self.new(event_repository, events = nil)
|
11
11
|
if events.is_a?(EventSet::Hash)
|
12
12
|
return EventSet::Hash.new event_repository, events.try(:events)
|
13
13
|
end
|
@@ -23,17 +23,17 @@ class CalendarAssistant
|
|
23
23
|
class Base
|
24
24
|
attr_reader :event_repository, :events
|
25
25
|
|
26
|
-
def initialize
|
26
|
+
def initialize(event_repository, events)
|
27
27
|
@event_repository = event_repository
|
28
28
|
@events = events
|
29
29
|
end
|
30
30
|
|
31
|
-
def ==
|
31
|
+
def ==(rhs)
|
32
32
|
return false unless rhs.is_a?(self.class)
|
33
33
|
self.event_repository == rhs.event_repository && self.events == rhs.events
|
34
34
|
end
|
35
35
|
|
36
|
-
def new
|
36
|
+
def new(new_events)
|
37
37
|
EventSet.new self.event_repository, new_events
|
38
38
|
end
|
39
39
|
|
@@ -45,28 +45,28 @@ class CalendarAssistant
|
|
45
45
|
end
|
46
46
|
|
47
47
|
class Hash < EventSet::Base
|
48
|
-
def ensure_keys
|
48
|
+
def ensure_keys(keys, only: false)
|
49
49
|
keys.each do |key|
|
50
50
|
events[key] = [] unless events.has_key?(key)
|
51
51
|
end
|
52
52
|
if only
|
53
53
|
events.keys.each do |key|
|
54
|
-
if !
|
54
|
+
if !keys.include? key
|
55
55
|
events.delete(key)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def []
|
61
|
+
def [](key)
|
62
62
|
events[key] ||= []
|
63
63
|
end
|
64
64
|
|
65
|
-
def []=
|
65
|
+
def []=(key, value)
|
66
66
|
events[key] = value
|
67
67
|
end
|
68
68
|
|
69
|
-
def available_blocks
|
69
|
+
def available_blocks(length: 1)
|
70
70
|
event_repository.in_tz do
|
71
71
|
dates = events.keys.sort
|
72
72
|
|
@@ -91,7 +91,7 @@ class CalendarAssistant
|
|
91
91
|
avail_time[date] << AvailableBlock.new(start: start_time, end: e.start_time)
|
92
92
|
end
|
93
93
|
start_time = [e.end_time, start_time].max
|
94
|
-
break if !
|
94
|
+
break if !start_time.during_business_hours?
|
95
95
|
end
|
96
96
|
|
97
97
|
if HasDuration.duration_in_seconds(start_time, end_time) >= length
|
@@ -105,18 +105,18 @@ class CalendarAssistant
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
108
|
-
def intersection
|
108
|
+
def intersection(other, length: 1)
|
109
109
|
set = new({})
|
110
110
|
set.ensure_keys(events.keys + other.events.keys)
|
111
111
|
set.events.keys.each do |date|
|
112
112
|
events[date].each do |event_a|
|
113
113
|
other.events[date].each do |event_b|
|
114
114
|
if event_a.contains?(event_b.start_time) ||
|
115
|
-
event_a.contains?(event_b.end_time-1) ||
|
115
|
+
event_a.contains?(event_b.end_time - 1) ||
|
116
116
|
event_b.contains?(event_a.start_time) ||
|
117
|
-
event_b.contains?(event_a.end_time-1)
|
117
|
+
event_b.contains?(event_a.end_time - 1)
|
118
118
|
start_time = [event_a.start_time, event_b.start_time].max
|
119
|
-
end_time
|
119
|
+
end_time = [event_a.end_time, event_b.end_time].min
|
120
120
|
if HasDuration.duration_in_seconds(start_time, end_time) >= length
|
121
121
|
set.events[date] << AvailableBlock.new(start: start_time, end: end_time)
|
122
122
|
end
|
@@ -12,9 +12,9 @@ require "launchy"
|
|
12
12
|
#
|
13
13
|
class CalendarAssistant
|
14
14
|
class ZoomLaunchy < Launchy::Application::Browser
|
15
|
-
ZOOM_URI_REGEXP = %r(https?://\w+.zoom.us/j/(\d+))
|
15
|
+
ZOOM_URI_REGEXP = %r(https?://\w+.zoom.us/j/(\d+)(\?(.*))?)
|
16
16
|
|
17
|
-
def self.handles?
|
17
|
+
def self.handles?(uri)
|
18
18
|
return true if ZOOM_URI_REGEXP.match(uri)
|
19
19
|
end
|
20
20
|
|
@@ -26,13 +26,16 @@ class CalendarAssistant
|
|
26
26
|
[find_executable("xdg-open")]
|
27
27
|
end
|
28
28
|
|
29
|
-
def open
|
29
|
+
def open(uri, options = {})
|
30
30
|
command = host_os_family.app_list(self).compact.first
|
31
31
|
if command.nil?
|
32
32
|
super uri, options
|
33
33
|
else
|
34
|
-
|
34
|
+
matches = ZOOM_URI_REGEXP.match(uri)
|
35
|
+
confno = matches[1]
|
36
|
+
params = matches[3]
|
35
37
|
url = "zoommtg://zoom.us/join?confno=#{confno}"
|
38
|
+
url += "&#{params}" if params
|
36
39
|
run command, [url]
|
37
40
|
end
|
38
41
|
end
|