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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/README.md +44 -22
  4. data/Rakefile +37 -6
  5. data/bin/calendar-assistant +3 -1
  6. data/lib/calendar_assistant.rb +14 -16
  7. data/lib/calendar_assistant/available_block.rb +1 -1
  8. data/lib/calendar_assistant/calendar_assistant.rb +21 -25
  9. data/lib/calendar_assistant/cli.rb +10 -10
  10. data/lib/calendar_assistant/cli/authorizer.rb +14 -14
  11. data/lib/calendar_assistant/cli/command_service.rb +2 -2
  12. data/lib/calendar_assistant/cli/commands.rb +47 -40
  13. data/lib/calendar_assistant/cli/config.rb +16 -17
  14. data/lib/calendar_assistant/cli/event_presenter.rb +2 -2
  15. data/lib/calendar_assistant/cli/event_set_presenter.rb +3 -3
  16. data/lib/calendar_assistant/cli/helpers.rb +7 -8
  17. data/lib/calendar_assistant/cli/linter_event_presenter.rb +3 -3
  18. data/lib/calendar_assistant/cli/linter_event_set_presenter.rb +4 -4
  19. data/lib/calendar_assistant/cli/printer.rb +15 -15
  20. data/lib/calendar_assistant/config.rb +11 -11
  21. data/lib/calendar_assistant/config/token_store.rb +4 -4
  22. data/lib/calendar_assistant/date_helpers.rb +1 -2
  23. data/lib/calendar_assistant/event.rb +54 -50
  24. data/lib/calendar_assistant/event_repository.rb +14 -15
  25. data/lib/calendar_assistant/event_repository_factory.rb +1 -1
  26. data/lib/calendar_assistant/event_set.rb +14 -14
  27. data/lib/calendar_assistant/extensions/google_apis_extensions.rb +1 -1
  28. data/lib/calendar_assistant/extensions/launchy_extensions.rb +7 -4
  29. data/lib/calendar_assistant/has_duration.rb +4 -5
  30. data/lib/calendar_assistant/lint_event_repository.rb +3 -3
  31. data/lib/calendar_assistant/location_config_validator.rb +3 -3
  32. data/lib/calendar_assistant/location_event_repository.rb +7 -8
  33. data/lib/calendar_assistant/predicate_collection.rb +1 -2
  34. data/lib/calendar_assistant/scheduler.rb +4 -5
  35. data/lib/calendar_assistant/string_helpers.rb +1 -1
  36. data/lib/calendar_assistant/version.rb +1 -1
  37. metadata +38 -30
@@ -7,7 +7,7 @@ class CalendarAssistant
7
7
  @options = options.dup
8
8
  @options[CalendarAssistant::Config::Keys::Options::CONTEXT] ||= context.to_s
9
9
 
10
- @config = CalendarAssistant::CLI::Config.new(options: @options)
10
+ @config = CalendarAssistant::CLI::Config.new(options: @options)
11
11
  @authorizer = {}
12
12
  @out = CalendarAssistant::CLI::Printer.new
13
13
  end
@@ -34,4 +34,4 @@ class CalendarAssistant
34
34
  end
35
35
  end
36
36
  end
37
- end
37
+ end
@@ -1,6 +1,8 @@
1
1
  class CalendarAssistant
2
2
  module CLI
3
3
  class Commands < Thor
4
+ HISTORY_FILE_PATH = File.join (ENV["CA_HOME"] || ENV["HOME"]), ".calendar-assistant.history"
5
+
4
6
  def self.will_create_a_service
5
7
  option CalendarAssistant::Config::Keys::Settings::PROFILE,
6
8
  type: :string,
@@ -11,7 +13,7 @@ class CalendarAssistant
11
13
  type: :string,
12
14
  banner: "FILENAME",
13
15
  desc: "Load events from a local file instead of Google Calendar",
14
- aliases: [ "-l" ]
16
+ aliases: ["-l"]
15
17
  end
16
18
 
17
19
  def self.has_events
@@ -19,12 +21,12 @@ class CalendarAssistant
19
21
  type: :string,
20
22
  desc: "Event properties that must be true (see README)",
21
23
  banner: "PROPERTY1[,PROPERTY2[,...]]",
22
- aliases: [ "-b" ]
24
+ aliases: ["-b"]
23
25
  option CalendarAssistant::Config::Keys::Options::MUST_NOT_BE,
24
26
  type: :string,
25
27
  desc: "Event properties that must be false (see README)",
26
28
  banner: "PROPERTY1[,PROPERTY2[,...]]",
27
- aliases: [ "-n" ]
29
+ aliases: ["-n"]
28
30
  end
29
31
 
30
32
  def self.has_multiple_calendars
@@ -50,7 +52,6 @@ class CalendarAssistant
50
52
  default: CalendarAssistant::Config::DEFAULT_SETTINGS[CalendarAssistant::Config::Keys::Options::FORMATTING],
51
53
  aliases: "-f"
52
54
 
53
-
54
55
  desc "version",
55
56
  "Display the version of calendar-assistant"
56
57
 
@@ -59,28 +60,26 @@ class CalendarAssistant
59
60
  command_service.out.puts CalendarAssistant::VERSION
60
61
  end
61
62
 
62
-
63
63
  desc "config",
64
64
  "Dump your configuration parameters (merge of defaults and overrides from #{CalendarAssistant::CLI::Config::CONFIG_FILE_PATH})"
65
65
 
66
66
  def config
67
67
  return if handle_help_args
68
68
  settings = CalendarAssistant::CLI::Config.new.settings
69
- command_service.out.puts TOML::Generator.new({CalendarAssistant::Config::Keys::SETTINGS => settings}).body
69
+ command_service.out.puts TOML::Generator.new({ CalendarAssistant::Config::Keys::SETTINGS => settings }).body
70
70
  end
71
71
 
72
-
73
72
  desc "setup",
74
73
  "Link your local calendar-assistant installation to a Google API Client"
75
74
  long_desc <<~EOD
76
- This command will walk you through setting up a Google Cloud
77
- Project, enabling the Google Calendar API, and saving the
78
- credentials necessary to access the API on behalf of users.
75
+ This command will walk you through setting up a Google Cloud
76
+ Project, enabling the Google Calendar API, and saving the
77
+ credentials necessary to access the API on behalf of users.
79
78
 
80
- If you already have downloaded client credentials, you don't
81
- need to run this command. Instead, rename the downloaded JSON
82
- file to `#{CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH}`
83
- EOD
79
+ If you already have downloaded client credentials, you don't
80
+ need to run this command. Instead, rename the downloaded JSON
81
+ file to `#{CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH}`
82
+ EOD
84
83
 
85
84
  def setup
86
85
  # TODO ugh see #34 for advice on how to clean this up
@@ -94,14 +93,14 @@ class CalendarAssistant
94
93
  command_service.out.launch "https://developers.google.com/calendar/quickstart/ruby"
95
94
  sleep 1
96
95
  command_service.out.puts <<~EOT
97
- Please click on "ENABLE THE GOOGLE CALENDAR API" and either create a new project or select an existing project.
96
+ Please click on "ENABLE THE GOOGLE CALENDAR API" and either create a new project or select an existing project.
98
97
 
99
- (If you create a new project, name it something like "yourname-calendar-assistant" so you remember why it exists.)
98
+ (If you create a new project, name it something like "yourname-calendar-assistant" so you remember why it exists.)
100
99
 
101
- Then click "DOWNLOAD CLIENT CONFIGURATION" to download the credentials to local disk.
100
+ Then click "DOWNLOAD CLIENT CONFIGURATION" to download the credentials to local disk.
102
101
 
103
- Finally, paste the contents of the downloaded file here (it should be a complete JSON object):
104
- EOT
102
+ Finally, paste the contents of the downloaded file here (it should be a complete JSON object):
103
+ EOT
105
104
 
106
105
  json = command_service.out.prompt "Paste JSON here"
107
106
  File.open(CalendarAssistant::CLI::Authorizer::CREDENTIALS_PATH, "w") do |f|
@@ -112,22 +111,21 @@ class CalendarAssistant
112
111
  command_service.out.puts "\nOK! Your next step is to run `calendar-assistant authorize`."
113
112
  end
114
113
 
115
-
116
114
  desc "authorize PROFILE_NAME",
117
115
  "create (or validate) a profile named NAME with calendar access"
118
116
  long_desc <<~EOD
119
- Create and authorize a named profile (e.g., "work", "home",
120
- "flastname@company.tld") to access your calendar.
117
+ Create and authorize a named profile (e.g., "work", "home",
118
+ "flastname@company.tld") to access your calendar.
121
119
 
122
- When setting up a profile, you'll be asked to visit a URL to
123
- authenticate, grant authorization, and generate and persist an
124
- access token.
120
+ When setting up a profile, you'll be asked to visit a URL to
121
+ authenticate, grant authorization, and generate and persist an
122
+ access token.
125
123
 
126
- In order for this to work, you'll need to have set up your API client
127
- credentials. Run `calendar-assistant help setup` for instructions.
128
- EOD
124
+ In order for this to work, you'll need to have set up your API client
125
+ credentials. Run `calendar-assistant help setup` for instructions.
126
+ EOD
129
127
 
130
- def authorize profile_name = nil
128
+ def authorize(profile_name = nil)
131
129
  return if handle_help_args
132
130
  return help! if profile_name.nil?
133
131
 
@@ -142,7 +140,8 @@ class CalendarAssistant
142
140
  has_multiple_calendars
143
141
 
144
142
  has_events
145
- def lint datespec = "today"
143
+
144
+ def lint(datespec = "today")
146
145
  calendar_assistant(datespec) do |ca, date, out|
147
146
  event_set = ca.lint_events date
148
147
  out.print_events ca, event_set, presenter_class: CalendarAssistant::CLI::LinterEventSetPresenter
@@ -159,14 +158,14 @@ class CalendarAssistant
159
158
  has_multiple_calendars
160
159
 
161
160
  has_events
162
- def show datespec = "today"
161
+
162
+ def show(datespec = "today")
163
163
  calendar_assistant(datespec) do |ca, date, out|
164
164
  event_set = ca.find_events date
165
165
  out.print_events ca, event_set
166
166
  end
167
167
  end
168
168
 
169
-
170
169
  desc "join [TIME]",
171
170
  "Open the URL for a video call attached to your meeting at time TIME (default 'now')"
172
171
  option CalendarAssistant::Config::Keys::Options::JOIN,
@@ -175,7 +174,8 @@ class CalendarAssistant
175
174
  will_create_a_service
176
175
 
177
176
  has_events
178
- def join timespec = "now"
177
+
178
+ def join(timespec = "now")
179
179
  return if handle_help_args
180
180
  set_formatting
181
181
  ca = CalendarAssistant.new command_service.config, service: command_service.service
@@ -191,20 +191,19 @@ class CalendarAssistant
191
191
  end
192
192
  end
193
193
 
194
-
195
194
  desc "location [DATE | DATERANGE]",
196
195
  "Show your location for a date or range of dates (default 'today')"
197
196
  will_create_a_service
198
197
 
199
198
  has_events
200
- def location datespec = "today"
199
+
200
+ def location(datespec = "today")
201
201
  calendar_assistant(datespec) do |ca, date, out|
202
202
  event_set = ca.find_location_events date
203
203
  out.print_events ca, event_set
204
204
  end
205
205
  end
206
206
 
207
-
208
207
  desc "location-set LOCATION [DATE | DATERANGE]",
209
208
  "Set your location to LOCATION for a date or range of dates (default 'today')"
210
209
  option CalendarAssistant::Config::Keys::Options::FORCE,
@@ -217,7 +216,8 @@ class CalendarAssistant
217
216
  will_create_a_service
218
217
  has_events
219
218
  has_multiple_calendars
220
- def location_set location = nil, datespec = "today"
219
+
220
+ def location_set(location = nil, datespec = "today")
221
221
  return help! if location.nil?
222
222
 
223
223
  calendar_assistant(datespec) do |ca, date, out|
@@ -226,7 +226,6 @@ class CalendarAssistant
226
226
  end
227
227
  end
228
228
 
229
-
230
229
  desc "availability [DATE | DATERANGE | TIMERANGE]",
231
230
  "Show your availability for a date or range of dates (default 'today')"
232
231
  option CalendarAssistant::Config::Keys::Settings::MEETING_LENGTH,
@@ -250,13 +249,21 @@ class CalendarAssistant
250
249
  has_multiple_calendars
251
250
  will_create_a_service
252
251
  has_events
253
- def availability datespec = "today"
252
+
253
+ def availability(datespec = "today")
254
254
  calendar_assistant(datespec) do |ca, date, out|
255
255
  event_set = ca.availability date
256
256
  out.print_available_blocks ca, event_set
257
257
  end
258
258
  end
259
259
 
260
+ desc "interactive", "interactive console for calendar assistant"
261
+ def interactive
262
+ return if handle_help_args
263
+ require "thor_repl"
264
+ ThorRepl.start(CalendarAssistant::CLI::Commands, history_file_path: HISTORY_FILE_PATH, prompt: Rainbow("calendar-asssistant> ").bright)
265
+ end
266
+
260
267
  private
261
268
 
262
269
  def set_formatting
@@ -267,7 +274,7 @@ class CalendarAssistant
267
274
  @command_service ||= CommandService.new(context: current_command_chain.first, options: options)
268
275
  end
269
276
 
270
- def calendar_assistant datespec = "today", &block
277
+ def calendar_assistant(datespec = "today", &block)
271
278
  return if handle_help_args
272
279
  set_formatting
273
280
  command_service.calendar_assistant(datespec, &block)
@@ -1,31 +1,30 @@
1
1
  class CalendarAssistant
2
2
  module CLI
3
3
  class Config < CalendarAssistant::Config
4
- class TomlParseFailure < CalendarAssistant::BaseException;
4
+ class TomlParseFailure < CalendarAssistant::BaseException
5
5
  end
6
- class NoConfigFileToPersist < CalendarAssistant::BaseException;
6
+
7
+ class NoConfigFileToPersist < CalendarAssistant::BaseException
7
8
  end
8
9
 
9
- CONFIG_FILE_PATH = File.join (ENV['CA_HOME'] || ENV["HOME"]), ".calendar-assistant"
10
+ CONFIG_FILE_PATH = File.join (ENV["CA_HOME"] || ENV["HOME"]), ".calendar-assistant"
10
11
  attr_reader :config_file_path
11
12
 
12
- def initialize options: {},
13
+ def initialize(options: {},
13
14
  config_file_path: CONFIG_FILE_PATH,
14
- defaults: DEFAULT_SETTINGS
15
-
16
-
15
+ defaults: DEFAULT_SETTINGS)
17
16
  @config_file_path = config_file_path
18
17
 
19
18
  user_config = if File.exist? config_file_path
20
- begin
21
- FileUtils.chmod 0600, config_file_path
22
- TOML.load_file config_file_path
23
- rescue Exception => e
24
- raise TomlParseFailure, "could not parse #{config_file_path}: #{e}"
25
- end
26
- else
27
- Hash.new
28
- end
19
+ begin
20
+ FileUtils.chmod 0600, config_file_path
21
+ TOML.load_file config_file_path
22
+ rescue Exception => e
23
+ raise TomlParseFailure, "could not parse TOML file '#{config_file_path}': #{e}"
24
+ end
25
+ else
26
+ Hash.new
27
+ end
29
28
  super(options: options, defaults: defaults, user_config: user_config)
30
29
  end
31
30
 
@@ -48,4 +47,4 @@ class CalendarAssistant
48
47
  end
49
48
  end
50
49
  end
51
- end
50
+ end
@@ -46,7 +46,7 @@ class CalendarAssistant
46
46
  date_ansi_codes << :bright if current?
47
47
  date_ansi_codes << :faint if past?
48
48
 
49
- date_ansi_codes.inject(rainbow.wrap(date)) {|text, ansi| text.send ansi}
49
+ date_ansi_codes.inject(rainbow.wrap(date)) { |text, ansi| text.send ansi }
50
50
  end
51
51
 
52
52
  def event_date
@@ -60,7 +60,7 @@ class CalendarAssistant
60
60
  end
61
61
  else
62
62
  if start_date == end_date
63
- sprintf("%s - %s", start.date_time.strftime("%Y-%m-%d %H:%M"), __getobj__.end.date_time.strftime("%H:%M"))
63
+ sprintf("%s - %s", start.date_time.strftime("%Y-%m-%d %H:%M"), __getobj__.end.date_time.strftime("%H:%M"))
64
64
  else
65
65
  sprintf("%s - %s", start.date_time.strftime("%Y-%m-%d %H:%M"), __getobj__.end.date_time.strftime("%Y-%m-%d %H:%M"))
66
66
  end
@@ -9,8 +9,8 @@ class CalendarAssistant
9
9
 
10
10
  def to_s
11
11
  [
12
- title,
13
- description
12
+ title,
13
+ description,
14
14
  ].join("\n")
15
15
  end
16
16
 
@@ -66,4 +66,4 @@ class CalendarAssistant
66
66
  end
67
67
  end
68
68
  end
69
- end
69
+ end
@@ -2,10 +2,10 @@
2
2
  class CalendarAssistant
3
3
  module CLI
4
4
  module Helpers
5
- class ChronicParseException < CalendarAssistant::BaseException;
5
+ class ChronicParseException < CalendarAssistant::BaseException
6
6
  end
7
7
 
8
- def self.parse_datespec userspec
8
+ def self.parse_datespec(userspec)
9
9
  start_userspec, end_userspec = userspec.split(/ ?\.\.\.? ?/)
10
10
 
11
11
  if end_userspec.nil?
@@ -25,21 +25,20 @@ class CalendarAssistant
25
25
 
26
26
  def self.now
27
27
  CalendarAssistant::Event.new(
28
- Google::Apis::CalendarV3::Event.new(start: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
29
- end: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
30
- summary: Rainbow(" now ").inverse.faint)
28
+ Google::Apis::CalendarV3::Event.new(start: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
29
+ end: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
30
+ summary: Rainbow(" now ").inverse.faint)
31
31
  )
32
32
  end
33
33
 
34
- def self.find_av_uri ca, timespec
34
+ def self.find_av_uri(ca, timespec)
35
35
  time = Chronic.parse timespec
36
36
  range = time..(time + 5.minutes)
37
37
  event_set = ca.find_events range
38
38
 
39
39
  [CalendarAssistant::Event::Response::ACCEPTED,
40
40
  CalendarAssistant::Event::Response::TENTATIVE,
41
- CalendarAssistant::Event::Response::NEEDS_ACTION,
42
- ].each do |response|
41
+ CalendarAssistant::Event::Response::NEEDS_ACTION].each do |response|
43
42
  event_set.events.reverse.select do |event|
44
43
  event.response_status == response
45
44
  end.each do |event|
@@ -12,12 +12,12 @@ class CalendarAssistant
12
12
  s += rainbow.wrap(sprintf(" | %s", view_summary)).bold
13
13
  s += event_attributes unless private?
14
14
  s = rainbow.wrap(Rainbow.uncolor(s)).faint.strike if declined?
15
- s += "\n #{' ' * (date_length + 2)}attendees: #{attendees}"
15
+ s += "\n #{" " * (date_length + 2)}attendees: #{attendees}"
16
16
  s
17
17
  end
18
18
 
19
19
  def attendees
20
- if required_other_attendees .length > SUMMARY_THRESHOLD
20
+ if required_other_attendees.length > SUMMARY_THRESHOLD
21
21
  summary_attendee_list
22
22
  else
23
23
  detailed_attendee_list
@@ -43,7 +43,7 @@ class CalendarAssistant
43
43
  end
44
44
 
45
45
  def required_other_attendees
46
- @required_other_attendees ||= (other_human_attendees || []).select {|a| !a.optional }
46
+ @required_other_attendees ||= (other_human_attendees || []).select { |a| !a.optional }
47
47
  end
48
48
 
49
49
  def response_emoji(response_status)
@@ -7,9 +7,9 @@ class CalendarAssistant
7
7
 
8
8
  def title
9
9
  rainbow.wrap(<<~OUT)
10
- #{event_repository.calendar.id}
11
- - looking for events that need attention
12
- - all times in #{event_repository.calendar.time_zone}
10
+ #{event_repository.calendar.id}
11
+ - looking for events that need attention
12
+ - all times in #{event_repository.calendar.time_zone}
13
13
  OUT
14
14
  end
15
15
 
@@ -20,4 +20,4 @@ class CalendarAssistant
20
20
  end
21
21
  end
22
22
  end
23
- end
23
+ end
@@ -2,16 +2,15 @@
2
2
  class CalendarAssistant
3
3
  module CLI
4
4
  class Printer
5
- class LaunchUrlException < CalendarAssistant::BaseException ; end
6
-
5
+ class LaunchUrlException < CalendarAssistant::BaseException; end
7
6
 
8
7
  attr_reader :io
9
8
 
10
- def initialize io = STDOUT
9
+ def initialize(io = STDOUT)
11
10
  @io = io
12
11
  end
13
12
 
14
- def launch url
13
+ def launch(url)
15
14
  begin
16
15
  Launchy.open(url)
17
16
  rescue Exception => e
@@ -19,11 +18,11 @@ class CalendarAssistant
19
18
  end
20
19
  end
21
20
 
22
- def puts *args
21
+ def puts(*args)
23
22
  io.puts(*args)
24
23
  end
25
24
 
26
- def prompt query, default = nil
25
+ def prompt(query, default = nil)
27
26
  loop do
28
27
  message = query
29
28
  message += " [#{default}]" if default
@@ -39,27 +38,28 @@ class CalendarAssistant
39
38
  end
40
39
  end
41
40
 
42
- def print_events ca, event_set, presenter_class: CLI::EventSetPresenter
41
+ def print_events(ca, event_set, presenter_class: CLI::EventSetPresenter)
43
42
  puts presenter_class.new(event_set, config: ca.config).to_s
44
43
  puts
45
44
  end
46
45
 
47
- def print_available_blocks ca, event_set, omit_title: false
48
- ers = ca.config.calendar_ids.map {|calendar_id| ca.event_repository calendar_id}
49
- time_zones = ers.map {|er| er.calendar.time_zone}.uniq
46
+ def print_available_blocks(ca, event_set, omit_title: false)
47
+ ers = ca.config.calendar_ids.map { |calendar_id| ca.event_repository calendar_id }
48
+ time_zones = ers.map { |er| er.calendar.time_zone }.uniq
50
49
 
51
50
  unless omit_title
52
- puts Rainbow(ers.map {|er| er.calendar.id}.join(", ")).italic
51
+ puts Rainbow(ers.map { |er| er.calendar.id }.join(", ")).italic
53
52
  puts Rainbow(sprintf("- looking for blocks at least %s long",
54
53
  ChronicDuration.output(
55
- ChronicDuration.parse(
56
- ca.config.setting(Config::Keys::Settings::MEETING_LENGTH))))).italic
54
+ ChronicDuration.parse(
55
+ ca.config.setting(Config::Keys::Settings::MEETING_LENGTH)
56
+ )
57
+ ))).italic
57
58
  time_zones.each do |time_zone|
58
59
  puts Rainbow(sprintf("- between %s and %s in %s",
59
60
  ca.config.setting(Config::Keys::Settings::START_OF_DAY),
60
61
  ca.config.setting(Config::Keys::Settings::END_OF_DAY),
61
- time_zone,
62
- )).italic
62
+ time_zone)).italic
63
63
  end
64
64
  puts
65
65
  end