calendar-assistant 0.9.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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