calendar-assistant 0.2.1 → 0.3.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 +1 -0
- data/README.md +280 -202
- data/Rakefile +9 -1
- data/lib/calendar_assistant.rb +35 -187
- data/lib/calendar_assistant/authorizer.rb +0 -3
- data/lib/calendar_assistant/calendar_assistant.rb +122 -0
- data/lib/calendar_assistant/cli.rb +84 -54
- data/lib/calendar_assistant/cli_helpers.rb +101 -38
- data/lib/calendar_assistant/config.rb +71 -12
- data/lib/calendar_assistant/date_helpers.rb +16 -0
- data/lib/calendar_assistant/event.rb +124 -14
- data/lib/calendar_assistant/event_repository.rb +30 -19
- data/lib/calendar_assistant/event_repository_factory.rb +7 -0
- data/lib/calendar_assistant/event_set.rb +124 -0
- data/lib/calendar_assistant/extensions/{event_date_time_extensions.rb → google_apis_extensions.rb} +5 -6
- data/lib/calendar_assistant/local_service.rb +82 -0
- data/lib/calendar_assistant/scheduler.rb +45 -0
- data/lib/calendar_assistant/string_helpers.rb +0 -2
- data/lib/calendar_assistant/version.rb +1 -1
- metadata +9 -4
- data/lib/calendar_assistant/extensions/event_extensions.rb +0 -71
@@ -1,8 +1,17 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class EventRepository
|
3
|
+
attr_reader :calendar, :calendar_id
|
4
|
+
|
3
5
|
def initialize(service, calendar_id)
|
4
6
|
@service = service
|
5
7
|
@calendar_id = calendar_id
|
8
|
+
@calendar = @service.get_calendar @calendar_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def in_tz &block
|
12
|
+
CalendarAssistant.in_tz calendar.time_zone do
|
13
|
+
yield
|
14
|
+
end
|
6
15
|
end
|
7
16
|
|
8
17
|
def find time_range
|
@@ -14,37 +23,39 @@ class CalendarAssistant
|
|
14
23
|
max_results: 2000,
|
15
24
|
)
|
16
25
|
if events.nil? || events.items.nil?
|
17
|
-
return
|
26
|
+
return CalendarAssistant::EventSet.new self, Array.new
|
18
27
|
end
|
19
|
-
events.items.map { |e| CalendarAssistant::Event.new(e) }
|
28
|
+
CalendarAssistant::EventSet.new self, events.items.map { |e| CalendarAssistant::Event.new(e) }
|
20
29
|
end
|
21
30
|
|
22
|
-
def
|
23
|
-
event =
|
24
|
-
@service.insert_event @calendar_id, event
|
31
|
+
def new event_attributes
|
32
|
+
event = Google::Apis::CalendarV3::Event.new DateHelpers.cast_dates(event_attributes)
|
25
33
|
CalendarAssistant::Event.new(event)
|
26
34
|
end
|
27
35
|
|
36
|
+
def create event_attributes
|
37
|
+
new(event_attributes).tap do |event|
|
38
|
+
@service.insert_event @calendar_id, event.__getobj__
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
28
42
|
def delete event
|
29
43
|
@service.delete_event @calendar_id, event.id
|
30
44
|
end
|
31
45
|
|
32
46
|
def update(event, attributes)
|
33
|
-
event.update! cast_dates(attributes)
|
34
|
-
@service.update_event @calendar_id, event.id, event
|
35
|
-
CalendarAssistant::Event.new(
|
47
|
+
event.update! DateHelpers.cast_dates(attributes)
|
48
|
+
updated_event = @service.update_event @calendar_id, event.id, event
|
49
|
+
CalendarAssistant::Event.new(updated_event)
|
36
50
|
end
|
37
51
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
object[key] = value
|
46
|
-
end
|
47
|
-
end
|
52
|
+
def available_block start_time, end_time
|
53
|
+
e = Google::Apis::CalendarV3::Event.new(
|
54
|
+
start: Google::Apis::CalendarV3::EventDateTime.new(date_time: start_time.in_time_zone(calendar.time_zone)),
|
55
|
+
end: Google::Apis::CalendarV3::EventDateTime.new(date_time: end_time.in_time_zone(calendar.time_zone)),
|
56
|
+
summary: "available"
|
57
|
+
)
|
58
|
+
CalendarAssistant::Event.new e
|
48
59
|
end
|
49
60
|
end
|
50
|
-
end
|
61
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
#
|
3
|
+
# note that `events` could be a few different data structures, depending.
|
4
|
+
#
|
5
|
+
# - it could be an Array of Events
|
6
|
+
# - it could be a Hash, e.g. Date => Array of Events
|
7
|
+
# - it could be a bare Event
|
8
|
+
#
|
9
|
+
class EventSet
|
10
|
+
def self.new event_repository, events=nil
|
11
|
+
if events.is_a?(::Hash)
|
12
|
+
return EventSet::Hash.new event_repository, events
|
13
|
+
end
|
14
|
+
if events.is_a?(::Array)
|
15
|
+
return EventSet::Array.new event_repository, events
|
16
|
+
end
|
17
|
+
return EventSet::Bare.new event_repository, events
|
18
|
+
end
|
19
|
+
|
20
|
+
class Base
|
21
|
+
attr_reader :event_repository, :events
|
22
|
+
|
23
|
+
def initialize event_repository, events
|
24
|
+
@event_repository = event_repository
|
25
|
+
@events = events
|
26
|
+
end
|
27
|
+
|
28
|
+
def == rhs
|
29
|
+
return false unless rhs.is_a?(self.class)
|
30
|
+
self.event_repository == rhs.event_repository && self.events == rhs.events
|
31
|
+
end
|
32
|
+
|
33
|
+
def new new_events
|
34
|
+
EventSet.new self.event_repository, new_events
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
return true if events.nil?
|
39
|
+
return events.length == 0 if events.is_a?(Enumerable)
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Hash < EventSet::Base
|
45
|
+
def ensure_keys keys, only: false
|
46
|
+
keys.each do |key|
|
47
|
+
events[key] = [] unless events.has_key?(key)
|
48
|
+
end
|
49
|
+
if only
|
50
|
+
events.keys.each do |key|
|
51
|
+
if ! keys.include? key
|
52
|
+
events.delete(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def available_blocks length: 1
|
59
|
+
event_repository.in_tz do
|
60
|
+
dates = events.keys.sort
|
61
|
+
|
62
|
+
# iterate over the days finding free chunks of time
|
63
|
+
_avail_time = dates.inject({}) do |avail_time, date|
|
64
|
+
avail_time[date] ||= []
|
65
|
+
date_events = events[date]
|
66
|
+
|
67
|
+
start_time = date.to_time.to_datetime +
|
68
|
+
BusinessTime::Config.beginning_of_workday.hour.hours +
|
69
|
+
BusinessTime::Config.beginning_of_workday.min.minutes
|
70
|
+
end_time = date.to_time.to_datetime +
|
71
|
+
BusinessTime::Config.end_of_workday.hour.hours +
|
72
|
+
BusinessTime::Config.end_of_workday.min.minutes
|
73
|
+
|
74
|
+
date_events.each do |e|
|
75
|
+
# ignore events that are outside my business day
|
76
|
+
next if Time.before_business_hours?(e.end_time.to_time)
|
77
|
+
next if Time.after_business_hours?(e.start_time.to_time)
|
78
|
+
|
79
|
+
if (e.start_time - start_time).days.to_i >= length
|
80
|
+
avail_time[date] << event_repository.available_block(start_time, e.start_time)
|
81
|
+
end
|
82
|
+
start_time = [e.end_time, start_time].max
|
83
|
+
break if ! start_time.during_business_hours?
|
84
|
+
end
|
85
|
+
|
86
|
+
if (end_time - start_time).days.to_i >= length
|
87
|
+
avail_time[date] << event_repository.available_block(start_time, end_time)
|
88
|
+
end
|
89
|
+
|
90
|
+
avail_time
|
91
|
+
end
|
92
|
+
|
93
|
+
new _avail_time
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def intersection other
|
98
|
+
set = new({})
|
99
|
+
set.ensure_keys(events.keys + other.events.keys)
|
100
|
+
set.events.keys.each do |date|
|
101
|
+
events[date].each do |event_a|
|
102
|
+
other.events[date].each do |event_b|
|
103
|
+
if event_a.contains?(event_b.start_time) ||
|
104
|
+
event_a.contains?(event_b.end_time-1) ||
|
105
|
+
event_b.contains?(event_a.start_time) ||
|
106
|
+
event_b.contains?(event_a.end_time-1)
|
107
|
+
start_time = [event_a.start_time, event_b.start_time].max
|
108
|
+
end_time = [event_a.end_time, event_b.end_time ].min
|
109
|
+
set.events[date] << event_repository.available_block(start_time, end_time)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
set
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Array < EventSet::Base
|
119
|
+
end
|
120
|
+
|
121
|
+
class Bare < EventSet::Base
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/calendar_assistant/extensions/{event_date_time_extensions.rb → google_apis_extensions.rb}
RENAMED
@@ -1,9 +1,8 @@
|
|
1
1
|
#
|
2
2
|
# this file extends the Google::EventDateTime class found in the "google_calendar" rubygem
|
3
3
|
#
|
4
|
-
|
4
|
+
autoload :Auth, "googleauth"
|
5
5
|
require "google/apis/calendar_v3"
|
6
|
-
require "time"
|
7
6
|
|
8
7
|
class Google::Apis::CalendarV3::EventDateTime
|
9
8
|
def to_date
|
@@ -22,10 +21,10 @@ class Google::Apis::CalendarV3::EventDateTime
|
|
22
21
|
@date_time.strftime "%Y-%m-%d %H:%M"
|
23
22
|
end
|
24
23
|
|
25
|
-
def ==
|
24
|
+
def == rhs
|
26
25
|
if @date
|
27
|
-
return to_date ==
|
26
|
+
return to_date == rhs.to_date
|
28
27
|
end
|
29
|
-
date_time ==
|
28
|
+
date_time == rhs.date_time
|
30
29
|
end
|
31
|
-
end
|
30
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
class LocalService
|
3
|
+
Result = Struct.new(:items)
|
4
|
+
attr_reader :file
|
5
|
+
|
6
|
+
def initialize(file: nil, load_events: true)
|
7
|
+
@file = file
|
8
|
+
if (@file && File.exists?(@file) && load_events)
|
9
|
+
@store = YAML::load_file(@file)
|
10
|
+
else
|
11
|
+
@store = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def list_events(calendar_id, options)
|
16
|
+
time_min = Time.parse(options.fetch(:time_min))
|
17
|
+
time_max = Time.parse(options.fetch(:time_max))
|
18
|
+
search_range = time_min..time_max
|
19
|
+
|
20
|
+
events = get_calendar_events(calendar_id).map do |id, event|
|
21
|
+
event_range = (event.start.date_time || event.start.date)..(event.end.date_time || event.end.date)
|
22
|
+
event if (event_range.first < search_range.last && event_range.last > search_range.first)
|
23
|
+
end.compact
|
24
|
+
Result.new(events)
|
25
|
+
end
|
26
|
+
|
27
|
+
def insert_event(calendar_id, event)
|
28
|
+
save do
|
29
|
+
get_calendar_events(calendar_id)[event.id] = event
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_event(calendar_id, id)
|
34
|
+
save do
|
35
|
+
get_calendar_events(calendar_id).delete(id)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_event(calendar_id, id, event)
|
40
|
+
save do
|
41
|
+
get_calendar_events(calendar_id)[id] = event
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_event(calendar_id, id)
|
46
|
+
get_calendar_events(calendar_id)[id]
|
47
|
+
end
|
48
|
+
|
49
|
+
def insert_calendar(calendar)
|
50
|
+
save do
|
51
|
+
@store[calendar.id] ||= {}
|
52
|
+
@store[calendar.id][:calendar] = calendar
|
53
|
+
@store[calendar.id][:events] = {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_calendar(calendar_id)
|
58
|
+
get_calendar_and_events(calendar_id)[:calendar]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def get_calendar_and_events(calendar_id)
|
64
|
+
return @store[calendar_id] if @store.has_key?(calendar_id)
|
65
|
+
return @store.first[1] if @store.length > 0
|
66
|
+
raise Google::Apis::ClientError.new("notFound")
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_calendar_events(calendar_id)
|
70
|
+
get_calendar_and_events(calendar_id)[:events]
|
71
|
+
end
|
72
|
+
|
73
|
+
def save
|
74
|
+
obj = yield
|
75
|
+
if (file)
|
76
|
+
File.open(file, "w") { |f| f.write(@store.to_yaml) }
|
77
|
+
end
|
78
|
+
|
79
|
+
obj
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
class Scheduler
|
3
|
+
attr_reader :ca, :ers
|
4
|
+
|
5
|
+
#
|
6
|
+
# class methods
|
7
|
+
#
|
8
|
+
def self.select_busy_events event_set
|
9
|
+
dates_events = Hash.new
|
10
|
+
event_set.events.each do |event|
|
11
|
+
if event.private? || event.accepted? || event.self?
|
12
|
+
date = event.start_date
|
13
|
+
dates_events[date] ||= []
|
14
|
+
dates_events[date] << event
|
15
|
+
end
|
16
|
+
end
|
17
|
+
event_set.new dates_events
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
#
|
22
|
+
# instance methods
|
23
|
+
#
|
24
|
+
def initialize calendar_assistant, event_repositories
|
25
|
+
@ca = calendar_assistant
|
26
|
+
@ers = Array(event_repositories)
|
27
|
+
end
|
28
|
+
|
29
|
+
def available_blocks time_range
|
30
|
+
avail = nil
|
31
|
+
ers.each do |er|
|
32
|
+
event_set = er.find time_range # array
|
33
|
+
event_set = Scheduler.select_busy_events event_set # hash
|
34
|
+
event_set.ensure_keys time_range.first.to_date .. time_range.last.to_date, only: true
|
35
|
+
|
36
|
+
length = ChronicDuration.parse(ca.config.setting(Config::Keys::Settings::MEETING_LENGTH))
|
37
|
+
ca.in_env do
|
38
|
+
set_avail = event_set.available_blocks(length: length)
|
39
|
+
avail = avail ? avail.intersection(set_avail) : set_avail
|
40
|
+
end
|
41
|
+
end
|
42
|
+
avail
|
43
|
+
end
|
44
|
+
end
|
45
|
+
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.3.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: 2018-
|
12
|
+
date: 2018-11-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: google-api-client
|
@@ -238,15 +238,20 @@ files:
|
|
238
238
|
- bin/calendar-assistant
|
239
239
|
- lib/calendar_assistant.rb
|
240
240
|
- lib/calendar_assistant/authorizer.rb
|
241
|
+
- lib/calendar_assistant/calendar_assistant.rb
|
241
242
|
- lib/calendar_assistant/cli.rb
|
242
243
|
- lib/calendar_assistant/cli_helpers.rb
|
243
244
|
- lib/calendar_assistant/config.rb
|
244
245
|
- lib/calendar_assistant/config/token_store.rb
|
246
|
+
- lib/calendar_assistant/date_helpers.rb
|
245
247
|
- lib/calendar_assistant/event.rb
|
246
248
|
- lib/calendar_assistant/event_repository.rb
|
247
|
-
- lib/calendar_assistant/
|
248
|
-
- lib/calendar_assistant/
|
249
|
+
- lib/calendar_assistant/event_repository_factory.rb
|
250
|
+
- lib/calendar_assistant/event_set.rb
|
251
|
+
- lib/calendar_assistant/extensions/google_apis_extensions.rb
|
249
252
|
- lib/calendar_assistant/extensions/rainbow_extensions.rb
|
253
|
+
- lib/calendar_assistant/local_service.rb
|
254
|
+
- lib/calendar_assistant/scheduler.rb
|
250
255
|
- lib/calendar_assistant/string_helpers.rb
|
251
256
|
- lib/calendar_assistant/version.rb
|
252
257
|
homepage: https://github.com/flavorjones/calendar-assistant
|
@@ -1,71 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# this file extends the Google::Event class found in the "google_calendar" rubygem
|
3
|
-
#
|
4
|
-
|
5
|
-
require "google/apis/calendar_v3"
|
6
|
-
require "time"
|
7
|
-
require "calendar_assistant/extensions/event_date_time_extensions"
|
8
|
-
|
9
|
-
class Google::Apis::CalendarV3::Event
|
10
|
-
module RealResponse
|
11
|
-
DECLINED = "declined"
|
12
|
-
ACCEPTED = "accepted"
|
13
|
-
NEEDS_ACTION = "needsAction"
|
14
|
-
TENTATIVE = "tentative"
|
15
|
-
end
|
16
|
-
|
17
|
-
module Response
|
18
|
-
include RealResponse
|
19
|
-
SELF = "self" # not part of Google's API, but useful to represent meetings-for-myself
|
20
|
-
end
|
21
|
-
|
22
|
-
module Transparency
|
23
|
-
TRANSPARENT = "transparent"
|
24
|
-
OPAQUE = "opaque"
|
25
|
-
end
|
26
|
-
|
27
|
-
module Visibility
|
28
|
-
DEFAULT = "default"
|
29
|
-
PUBLIC = "public"
|
30
|
-
PRIVATE = "private"
|
31
|
-
end
|
32
|
-
|
33
|
-
def update **args
|
34
|
-
# this should be in the google API classes, IMHO
|
35
|
-
update!(**args)
|
36
|
-
self
|
37
|
-
end
|
38
|
-
|
39
|
-
def human_attendees
|
40
|
-
return nil if attendees.nil?
|
41
|
-
attendees.select { |a| ! a.resource }
|
42
|
-
end
|
43
|
-
|
44
|
-
def attendee id
|
45
|
-
return nil if attendees.nil?
|
46
|
-
attendees.find do |attendee|
|
47
|
-
attendee.email == id
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def response_status
|
52
|
-
return Response::SELF if attendees.nil?
|
53
|
-
attendees.each do |attendee|
|
54
|
-
return attendee.response_status if attendee.self
|
55
|
-
end
|
56
|
-
nil
|
57
|
-
end
|
58
|
-
|
59
|
-
def av_uri
|
60
|
-
@av_uri ||= begin
|
61
|
-
description_link = CalendarAssistant::StringHelpers.find_uri_for_domain(description, "zoom.us")
|
62
|
-
return description_link if description_link
|
63
|
-
|
64
|
-
location_link = CalendarAssistant::StringHelpers.find_uri_for_domain(location, "zoom.us")
|
65
|
-
return location_link if location_link
|
66
|
-
|
67
|
-
return hangout_link if hangout_link
|
68
|
-
nil
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|