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.
@@ -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 create event_attributes
23
- event = GCal::Event.new cast_dates(event_attributes)
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(event)
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
- private
39
-
40
- def cast_dates attributes
41
- attributes.each_with_object({}) do |(key, value), object|
42
- if value.is_a?(Date)
43
- object[key] = GCal::EventDateTime.new(date: value.iso8601)
44
- else
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,7 @@
1
+ class CalendarAssistant
2
+ class EventRepositoryFactory
3
+ def self.new_event_repository service, calendar_id
4
+ EventRepository.new service, calendar_id
5
+ end
6
+ end
7
+ 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
@@ -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 == lhs
24
+ def == rhs
26
25
  if @date
27
- return to_date == lhs.to_date
26
+ return to_date == rhs.to_date
28
27
  end
29
- date_time == lhs.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
@@ -1,5 +1,3 @@
1
- require "uri"
2
-
3
1
  class CalendarAssistant
4
2
  module StringHelpers
5
3
  def self.find_uri_for_domain string, domain
@@ -1,3 +1,3 @@
1
1
  class CalendarAssistant
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  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.2.1
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-10-31 00:00:00.000000000 Z
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/extensions/event_date_time_extensions.rb
248
- - lib/calendar_assistant/extensions/event_extensions.rb
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